From a3763ef8a7d0b2f932f74471d70d54590f271aea Mon Sep 17 00:00:00 2001 From: Ivcho Date: Tue, 23 Jun 2026 13:47:56 +0300 Subject: [PATCH 01/17] Implemented fully working department view # Conflicts: # uniplanWeb/src/app/core/shared/main-panel/main-panel.html # uniplanWeb/src/app/core/shared/main-panel/main-panel.ts # uniplanWeb/src/app/core/shared/navmenu-component/navmenu-component.html # uniplanWeb/src/app/core/shared/navmenu-component/navmenu-component.ts --- .../src/app/core/interfaces/department-elm.ts | 6 ++ .../department-add-form.html | 21 ++++ .../department-add-form.scss | 14 +++ .../department-add-form.spec.ts | 18 ++++ .../department-add-form.ts | 71 ++++++++++++++ .../department-delete-form.html | 8 ++ .../department-delete-form.scss | 0 .../department-delete-form.spec.ts | 18 ++++ .../department-delete-form.ts | 37 +++++++ .../department-edit-form.html | 19 ++++ .../department-edit-form.scss | 13 +++ .../department-edit-form.spec.ts | 18 ++++ .../department-edit-form.ts | 77 +++++++++++++++ .../department-filters.html | 14 +++ .../department-filters.scss | 14 +++ .../department-filters.spec.ts | 18 ++++ .../department-filters/department-filters.ts | 30 ++++++ .../department-options.html | 2 + .../department-options.scss | 3 + .../department-options.spec.ts | 18 ++++ .../department-options/department-options.ts | 29 ++++++ .../department/department-service.spec.ts | 12 +++ .../features/department/department-service.ts | 61 ++++++++++++ .../department-table/department-table.html | 61 ++++++++++++ .../department-table/department-table.scss | 74 ++++++++++++++ .../department-table/department-table.spec.ts | 18 ++++ .../department-table/department-table.ts | 98 +++++++++++++++++++ 27 files changed, 772 insertions(+) create mode 100644 uniplanWeb/src/app/core/interfaces/department-elm.ts create mode 100644 uniplanWeb/src/app/features/department/department-add-form/department-add-form.html create mode 100644 uniplanWeb/src/app/features/department/department-add-form/department-add-form.scss create mode 100644 uniplanWeb/src/app/features/department/department-add-form/department-add-form.spec.ts create mode 100644 uniplanWeb/src/app/features/department/department-add-form/department-add-form.ts create mode 100644 uniplanWeb/src/app/features/department/department-delete-form/department-delete-form.html create mode 100644 uniplanWeb/src/app/features/department/department-delete-form/department-delete-form.scss create mode 100644 uniplanWeb/src/app/features/department/department-delete-form/department-delete-form.spec.ts create mode 100644 uniplanWeb/src/app/features/department/department-delete-form/department-delete-form.ts create mode 100644 uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.html create mode 100644 uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.scss create mode 100644 uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.spec.ts create mode 100644 uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.ts create mode 100644 uniplanWeb/src/app/features/department/department-filters/department-filters.html create mode 100644 uniplanWeb/src/app/features/department/department-filters/department-filters.scss create mode 100644 uniplanWeb/src/app/features/department/department-filters/department-filters.spec.ts create mode 100644 uniplanWeb/src/app/features/department/department-filters/department-filters.ts create mode 100644 uniplanWeb/src/app/features/department/department-options/department-options.html create mode 100644 uniplanWeb/src/app/features/department/department-options/department-options.scss create mode 100644 uniplanWeb/src/app/features/department/department-options/department-options.spec.ts create mode 100644 uniplanWeb/src/app/features/department/department-options/department-options.ts create mode 100644 uniplanWeb/src/app/features/department/department-service.spec.ts create mode 100644 uniplanWeb/src/app/features/department/department-service.ts create mode 100644 uniplanWeb/src/app/features/department/department-table/department-table.html create mode 100644 uniplanWeb/src/app/features/department/department-table/department-table.scss create mode 100644 uniplanWeb/src/app/features/department/department-table/department-table.spec.ts create mode 100644 uniplanWeb/src/app/features/department/department-table/department-table.ts diff --git a/uniplanWeb/src/app/core/interfaces/department-elm.ts b/uniplanWeb/src/app/core/interfaces/department-elm.ts new file mode 100644 index 0000000..a81bfe5 --- /dev/null +++ b/uniplanWeb/src/app/core/interfaces/department-elm.ts @@ -0,0 +1,6 @@ +export interface DepartmentElm { + id: string; + departmentName: string; + facultyId: string; + position: number; +} diff --git a/uniplanWeb/src/app/features/department/department-add-form/department-add-form.html b/uniplanWeb/src/app/features/department/department-add-form/department-add-form.html new file mode 100644 index 0000000..902767d --- /dev/null +++ b/uniplanWeb/src/app/features/department/department-add-form/department-add-form.html @@ -0,0 +1,21 @@ + + + + + Име: + + + + + Факултет: + + @for (fac of faculties; track fac) { + + {{ fac.facultyName }} + + } + + + + + diff --git a/uniplanWeb/src/app/features/department/department-add-form/department-add-form.scss b/uniplanWeb/src/app/features/department/department-add-form/department-add-form.scss new file mode 100644 index 0000000..9b05a56 --- /dev/null +++ b/uniplanWeb/src/app/features/department/department-add-form/department-add-form.scss @@ -0,0 +1,14 @@ +mat-form-field { + width: 100%; +} + +mat-dialog-title { + font-size: 20px; + font-weight: bold; + color: #3f51b5; +} + +input { + border-radius: 10px; + width: 100%; +} diff --git a/uniplanWeb/src/app/features/department/department-add-form/department-add-form.spec.ts b/uniplanWeb/src/app/features/department/department-add-form/department-add-form.spec.ts new file mode 100644 index 0000000..77e8e01 --- /dev/null +++ b/uniplanWeb/src/app/features/department/department-add-form/department-add-form.spec.ts @@ -0,0 +1,18 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DepartmentAddForm } from './department-add-form'; + +describe('DepartmentAddForm', () => { + let component: DepartmentAddForm; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [DepartmentAddForm], + }).compileComponents(); + + fixture = TestBed.createComponent(DepartmentAddForm); + component = fixture.componentInstance; + fixture.detectChanges(); + }); +}); diff --git a/uniplanWeb/src/app/features/department/department-add-form/department-add-form.ts b/uniplanWeb/src/app/features/department/department-add-form/department-add-form.ts new file mode 100644 index 0000000..30fa2bd --- /dev/null +++ b/uniplanWeb/src/app/features/department/department-add-form/department-add-form.ts @@ -0,0 +1,71 @@ +import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core'; +import { AddForm } from '../../../core/shared/add-form/add-form'; +import { MatDialogModule, MatDialogRef } from '@angular/material/dialog'; +import { + MatFormField, + MatFormFieldModule, + MatLabel, +} from '@angular/material/form-field'; +import { FormsModule } from '@angular/forms'; +import { MatInputModule } from '@angular/material/input'; +import { MatSelectModule } from '@angular/material/select'; +import { DepartmentService } from '../department-service'; +import { FacultyElm } from '../../../core/interfaces/faculty-elm'; +import { FacultyService } from '../../faculty/faculty-service'; + +@Component({ + selector: 'app-department-add-form', + imports: [ + MatDialogModule, + MatFormField, + MatLabel, + FormsModule, + MatInputModule, + AddForm, + MatFormFieldModule, + MatSelectModule, + ], + templateUrl: './department-add-form.html', + changeDetection: ChangeDetectionStrategy.Eager, + styleUrl: './department-add-form.scss', +}) +export class DepartmentAddForm implements OnInit { + departmentName = ''; + facultyId = ''; + + faculties: FacultyElm[] = []; + + constructor( + private dialogRef: MatDialogRef, + private departmentService: DepartmentService, + private facultyService: FacultyService + ) {} + + ngOnInit(): void { + this.facultyService.getFaculties().subscribe({ + next: (data) => { + this.faculties = data; + }, + error: (err) => console.error('Failed to load faculties', err), + }); + } + + save() { + if (!this.departmentName.trim() || !this.facultyId) { + alert('Please fill all fields.'); + return; + } + + this.departmentService + .createDepartment({ + departmentName: this.departmentName, + facultyId: this.facultyId, + }) + .subscribe({ + next: () => { + this.dialogRef.close(true); + }, + error: () => alert('Failed to create department.'), + }); + } +} diff --git a/uniplanWeb/src/app/features/department/department-delete-form/department-delete-form.html b/uniplanWeb/src/app/features/department/department-delete-form/department-delete-form.html new file mode 100644 index 0000000..d92524f --- /dev/null +++ b/uniplanWeb/src/app/features/department/department-delete-form/department-delete-form.html @@ -0,0 +1,8 @@ + + +

+ Сигурни ли сте, че искате да изтриете катедра с име: + {{ data.departmentName }}? +

+
+
diff --git a/uniplanWeb/src/app/features/department/department-delete-form/department-delete-form.scss b/uniplanWeb/src/app/features/department/department-delete-form/department-delete-form.scss new file mode 100644 index 0000000..e69de29 diff --git a/uniplanWeb/src/app/features/department/department-delete-form/department-delete-form.spec.ts b/uniplanWeb/src/app/features/department/department-delete-form/department-delete-form.spec.ts new file mode 100644 index 0000000..022983d --- /dev/null +++ b/uniplanWeb/src/app/features/department/department-delete-form/department-delete-form.spec.ts @@ -0,0 +1,18 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DepartmentDeleteForm } from './department-delete-form'; + +describe('DepartmentDeleteForm', () => { + let component: DepartmentDeleteForm; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [DepartmentDeleteForm], + }).compileComponents(); + + fixture = TestBed.createComponent(DepartmentDeleteForm); + component = fixture.componentInstance; + fixture.detectChanges(); + }); +}); diff --git a/uniplanWeb/src/app/features/department/department-delete-form/department-delete-form.ts b/uniplanWeb/src/app/features/department/department-delete-form/department-delete-form.ts new file mode 100644 index 0000000..38cc0a7 --- /dev/null +++ b/uniplanWeb/src/app/features/department/department-delete-form/department-delete-form.ts @@ -0,0 +1,37 @@ +import { Component, Inject, ChangeDetectionStrategy } from '@angular/core'; +import { DeleteForm } from '../../../core/shared/delete-form/delete-form'; +import { + MAT_DIALOG_DATA, + MatDialogModule, + MatDialogRef, +} from '@angular/material/dialog'; +import { DepartmentService } from '../department-service'; + +@Component({ + selector: 'app-department-delete-form', + imports: [DeleteForm, MatDialogModule], + templateUrl: './department-delete-form.html', + changeDetection: ChangeDetectionStrategy.Eager, + styleUrl: './department-delete-form.scss', +}) +export class DepartmentDeleteForm { + constructor( + private departmentService: DepartmentService, + private dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) + public data: { id: string; departmentName: string } + ) {} + + deleteDepartment(): void { + this.departmentService + .deleteDepartment(this.data.id) + .subscribe({ + next: () => { + this.dialogRef.close(true); + }, + error: () => { + alert('Възникна грешка при изтриването на катедрата.'); + }, + }); + } +} diff --git a/uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.html b/uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.html new file mode 100644 index 0000000..8882efe --- /dev/null +++ b/uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.html @@ -0,0 +1,19 @@ + + + + Име: + + + + + Факултет: + + @for (fac of faculties; track fac) { + + {{ fac.facultyName }} + + } + + + + diff --git a/uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.scss b/uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.scss new file mode 100644 index 0000000..887baec --- /dev/null +++ b/uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.scss @@ -0,0 +1,13 @@ +mat-form-field { + width: 100%; +} + +mat-dialog-title { + font-size: 20px; + font-weight: bold; + color: #3f51b5; +} + +input { + border-radius: 10px; +} diff --git a/uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.spec.ts b/uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.spec.ts new file mode 100644 index 0000000..38bfd09 --- /dev/null +++ b/uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.spec.ts @@ -0,0 +1,18 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DepartmentEditForm } from './department-edit-form'; + +describe('DepartmentEditForm', () => { + let component: DepartmentEditForm; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [DepartmentEditForm], + }).compileComponents(); + + fixture = TestBed.createComponent(DepartmentEditForm); + component = fixture.componentInstance; + fixture.detectChanges(); + }); +}); diff --git a/uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.ts b/uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.ts new file mode 100644 index 0000000..df91c3d --- /dev/null +++ b/uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.ts @@ -0,0 +1,77 @@ +import { Component, Inject, OnInit, ChangeDetectionStrategy } from '@angular/core'; +import { EditForm } from '../../../core/shared/edit-form/edit-form'; +import { MatFormField, MatLabel } from '@angular/material/form-field'; +import { + MAT_DIALOG_DATA, + MatDialogModule, + MatDialogRef, +} from '@angular/material/dialog'; +import { MatOptionModule } from '@angular/material/core'; +import { MatSelectModule } from '@angular/material/select'; +import { MatInputModule } from '@angular/material/input'; +import { FormsModule } from '@angular/forms'; +import { DepartmentService } from '../department-service'; +import { FacultyService } from '../../faculty/faculty-service'; +import { FacultyElm } from '../../../core/interfaces/faculty-elm'; + +@Component({ + selector: 'app-department-edit-form', + imports: [ + EditForm, + MatDialogModule, + MatFormField, + MatLabel, + FormsModule, + MatInputModule, + MatSelectModule, + MatOptionModule, + ], + templateUrl: './department-edit-form.html', + changeDetection: ChangeDetectionStrategy.Eager, + styleUrl: './department-edit-form.scss', +}) +export class DepartmentEditForm implements OnInit { + departmentName = ''; + facultyId = ''; + + faculties: FacultyElm[] = []; + + constructor( + private dialogRef: MatDialogRef, + private departmentService: DepartmentService, + private facultyService: FacultyService, + @Inject(MAT_DIALOG_DATA) + public data: { + id: string; + departmentName: string; + facultyId?: string; + } + ) { + this.departmentName = data.departmentName; + this.facultyId = data.facultyId || ''; + } + + ngOnInit(): void { + this.facultyService.getFaculties().subscribe({ + next: (data) => (this.faculties = data), + error: (err) => console.error('Failed to load faculties', err), + }); + } + + save() { + if (!this.departmentName.trim()) { + alert('Please enter the department name.'); + return; + } + + this.departmentService + .editDepartment(this.data.id, { + departmentName: this.departmentName, + facultyId: this.facultyId, + }) + .subscribe({ + next: () => this.dialogRef.close(true), + error: () => alert('Failed to update department.'), + }); + } +} diff --git a/uniplanWeb/src/app/features/department/department-filters/department-filters.html b/uniplanWeb/src/app/features/department/department-filters/department-filters.html new file mode 100644 index 0000000..68e3f5a --- /dev/null +++ b/uniplanWeb/src/app/features/department/department-filters/department-filters.html @@ -0,0 +1,14 @@ +
+ + + + +
diff --git a/uniplanWeb/src/app/features/department/department-filters/department-filters.scss b/uniplanWeb/src/app/features/department/department-filters/department-filters.scss new file mode 100644 index 0000000..bcba9e9 --- /dev/null +++ b/uniplanWeb/src/app/features/department/department-filters/department-filters.scss @@ -0,0 +1,14 @@ +.filters-panel { + display: flex; + flex-wrap: wrap; + gap: 1%; + justify-content: flex-end; + align-items: flex-end; + + height: auto; + padding: 0 10px; +} + +::ng-deep .mat-form-field-infix { + padding: 4px 0 !important; +} diff --git a/uniplanWeb/src/app/features/department/department-filters/department-filters.spec.ts b/uniplanWeb/src/app/features/department/department-filters/department-filters.spec.ts new file mode 100644 index 0000000..144ffeb --- /dev/null +++ b/uniplanWeb/src/app/features/department/department-filters/department-filters.spec.ts @@ -0,0 +1,18 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DepartmentFilters } from './department-filters'; + +describe('DepartmentFilters', () => { + let component: DepartmentFilters; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [DepartmentFilters], + }).compileComponents(); + + fixture = TestBed.createComponent(DepartmentFilters); + component = fixture.componentInstance; + fixture.detectChanges(); + }); +}); diff --git a/uniplanWeb/src/app/features/department/department-filters/department-filters.ts b/uniplanWeb/src/app/features/department/department-filters/department-filters.ts new file mode 100644 index 0000000..e90f1bc --- /dev/null +++ b/uniplanWeb/src/app/features/department/department-filters/department-filters.ts @@ -0,0 +1,30 @@ +import { Component, EventEmitter, Input, Output, ChangeDetectionStrategy } from '@angular/core'; +import { FiltersForm } from '../../../core/shared/filters-form/filters-form'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatInputModule } from '@angular/material/input'; +import { MatIconModule } from '@angular/material/icon'; +import { InputFilter } from '../../../core/shared/input-filter/input-filter'; + +@Component({ + selector: 'app-department-filters', + imports: [ + FiltersForm, + MatFormFieldModule, + MatInputModule, + MatIconModule, + InputFilter, + ], + templateUrl: './department-filters.html', + changeDetection: ChangeDetectionStrategy.Eager, + styleUrl: './department-filters.scss', +}) +export class DepartmentFilters { + @Input() internalSearchText = ''; + + @Input() faculties: { id: string; name: string }[] = []; + + @Input() selectedFaculty = ''; + + @Output() facultyChange = new EventEmitter(); + @Output() searchTextChange = new EventEmitter(); +} diff --git a/uniplanWeb/src/app/features/department/department-options/department-options.html b/uniplanWeb/src/app/features/department/department-options/department-options.html new file mode 100644 index 0000000..1bb70bc --- /dev/null +++ b/uniplanWeb/src/app/features/department/department-options/department-options.html @@ -0,0 +1,2 @@ + diff --git a/uniplanWeb/src/app/features/department/department-options/department-options.scss b/uniplanWeb/src/app/features/department/department-options/department-options.scss new file mode 100644 index 0000000..e170cab --- /dev/null +++ b/uniplanWeb/src/app/features/department/department-options/department-options.scss @@ -0,0 +1,3 @@ +.add-button :hover { + background-color: #e3f2fd; +} diff --git a/uniplanWeb/src/app/features/department/department-options/department-options.spec.ts b/uniplanWeb/src/app/features/department/department-options/department-options.spec.ts new file mode 100644 index 0000000..e13c13a --- /dev/null +++ b/uniplanWeb/src/app/features/department/department-options/department-options.spec.ts @@ -0,0 +1,18 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DepartmentOptions } from './department-options'; + +describe('DepartmentOptions', () => { + let component: DepartmentOptions; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [DepartmentOptions], + }).compileComponents(); + + fixture = TestBed.createComponent(DepartmentOptions); + component = fixture.componentInstance; + fixture.detectChanges(); + }); +}); diff --git a/uniplanWeb/src/app/features/department/department-options/department-options.ts b/uniplanWeb/src/app/features/department/department-options/department-options.ts new file mode 100644 index 0000000..a69a1aa --- /dev/null +++ b/uniplanWeb/src/app/features/department/department-options/department-options.ts @@ -0,0 +1,29 @@ +import { Component, ChangeDetectionStrategy } from '@angular/core'; +import { MatDialog } from '@angular/material/dialog'; +import { DepartmentAddForm } from '../department-add-form/department-add-form'; +import { MatTableModule } from '@angular/material/table'; +import { MatIconModule } from '@angular/material/icon'; +import { MatButtonModule } from '@angular/material/button'; +import { AddButton } from '../../../core/shared/add-button/add-button'; + +@Component({ + selector: 'app-department-options', + imports: [ + MatTableModule, + MatIconModule, + MatButtonModule, + AddButton, + ], + templateUrl: './department-options.html', + changeDetection: ChangeDetectionStrategy.Eager, + styleUrl: './department-options.scss', +}) +export class DepartmentOptions { + constructor(private dialog: MatDialog) {} + + openAddForm() { + this.dialog.open(DepartmentAddForm, { + width: '400px', + }); + } +} diff --git a/uniplanWeb/src/app/features/department/department-service.spec.ts b/uniplanWeb/src/app/features/department/department-service.spec.ts new file mode 100644 index 0000000..6342a3c --- /dev/null +++ b/uniplanWeb/src/app/features/department/department-service.spec.ts @@ -0,0 +1,12 @@ +import { TestBed } from '@angular/core/testing'; + +import { DepartmentService } from './department-service'; + +describe('DepartmentService', () => { + let service: DepartmentService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(DepartmentService); + }); +}); diff --git a/uniplanWeb/src/app/features/department/department-service.ts b/uniplanWeb/src/app/features/department/department-service.ts new file mode 100644 index 0000000..acd16d1 --- /dev/null +++ b/uniplanWeb/src/app/features/department/department-service.ts @@ -0,0 +1,61 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { map, Observable, Subject } from 'rxjs'; +import { DepartmentElm } from '../../core/interfaces/department-elm'; + +@Injectable({ + providedIn: 'root', +}) +export class DepartmentService { + private apiUrl = 'http://localhost:8080/api/departments'; + + refreshNeeded = new Subject(); + + constructor(private http: HttpClient) {} + + getDepartments(): Observable { + return this.http.get(this.apiUrl).pipe( + map((departments) => + departments.map((dept, index) => ({ + id: dept.id, + departmentName: dept.departmentName, + facultyId: dept.facultyId, + position: index + 1, + })) + ) + ); + } + + createDepartment(data: { + departmentName: string; + facultyId: string; + }): Observable { + return this.http.post(`${this.apiUrl}`, data).pipe( + map((res) => { + this.refreshNeeded.next(); + return res; + }) + ); + } + + editDepartment( + id: string, + data: { departmentName: string; facultyId: string } + ): Observable { + return this.http.put(`${this.apiUrl}/${id}`, data).pipe( + map((res) => { + this.refreshNeeded.next(); + return res; + }) + ); + } + + deleteDepartment(id: string): Observable { + return this.http.delete(`${this.apiUrl}/${id}`).pipe( + map((res) => { + this.refreshNeeded.next(); + return res; + }) + ); + } +} diff --git a/uniplanWeb/src/app/features/department/department-table/department-table.html b/uniplanWeb/src/app/features/department/department-table/department-table.html new file mode 100644 index 0000000..4070b1b --- /dev/null +++ b/uniplanWeb/src/app/features/department/department-table/department-table.html @@ -0,0 +1,61 @@ +
+ + + + + + + + + + + + + + + + + + +
No. + Катедра + Факултет + Действия +
+ +
+ + + + + + + + + + + + + + + + + + +
+ {{element.position}} + {{element.departmentName}} + {{ getFacultyName(element.facultyId) }} + + +
+
+
diff --git a/uniplanWeb/src/app/features/department/department-table/department-table.scss b/uniplanWeb/src/app/features/department/department-table/department-table.scss new file mode 100644 index 0000000..6bafc2a --- /dev/null +++ b/uniplanWeb/src/app/features/department/department-table/department-table.scss @@ -0,0 +1,74 @@ +.table-wrapper { + border-radius: 10px; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); + overflow: hidden; + background-color: #fff; +} + +.table-header { + width: 100%; + table-layout: fixed; +} + +.scrollable-body { + max-height: 510px; + overflow-y: auto; + overflow-x: hidden; +} + +@media (min-width: 900px) { + .scrollable-body { + max-height: 510px; + } +} + +@media (min-width: 1800px) { + .scrollable-body { + max-height: 680px; + } +} + +.table-body { + width: 100%; + table-layout: fixed; + border-collapse: collapse; +} + +.table-body tr:hover td { + background-color: #f8f8f8; + cursor: pointer; +} + +th, +td { + text-align: center; + padding: 8px 10px; + word-wrap: break-word; +} + +th { + background-color: #f7f7f7 !important; +} + +td { + background-color: #fff; +} + +.col-position { + width: 80px; + border-right: 1px solid #e0e0e0; +} + +.col-name { + width: 300px; + border-right: 1px solid #e0e0e0; +} + +.col-faculty { + width: 350px; + border-right: 1px solid #e0e0e0; +} + +.col-actions { + width: 150px; +} diff --git a/uniplanWeb/src/app/features/department/department-table/department-table.spec.ts b/uniplanWeb/src/app/features/department/department-table/department-table.spec.ts new file mode 100644 index 0000000..a4b6aa9 --- /dev/null +++ b/uniplanWeb/src/app/features/department/department-table/department-table.spec.ts @@ -0,0 +1,18 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DepartmentTable } from './department-table'; + +describe('DepartmentTable', () => { + let component: DepartmentTable; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [DepartmentTable], + }).compileComponents(); + + fixture = TestBed.createComponent(DepartmentTable); + component = fixture.componentInstance; + fixture.detectChanges(); + }); +}); diff --git a/uniplanWeb/src/app/features/department/department-table/department-table.ts b/uniplanWeb/src/app/features/department/department-table/department-table.ts new file mode 100644 index 0000000..35a4ddd --- /dev/null +++ b/uniplanWeb/src/app/features/department/department-table/department-table.ts @@ -0,0 +1,98 @@ +import { Component, Input, OnInit, ChangeDetectionStrategy } from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { MatIconModule } from '@angular/material/icon'; +import { MatTableModule } from '@angular/material/table'; +import { DepartmentElm } from '../../../core/interfaces/department-elm'; +import { DepartmentEditForm } from '../department-edit-form/department-edit-form'; +import { MatDialog } from '@angular/material/dialog'; +import { DepartmentDeleteForm } from '../department-delete-form/department-delete-form'; +import { DepartmentService } from '../department-service'; +import { FacultyService } from '../../faculty/faculty-service'; + +@Component({ + selector: 'app-department-table', + imports: [MatTableModule, MatIconModule, MatButtonModule], + templateUrl: './department-table.html', + changeDetection: ChangeDetectionStrategy.Eager, + styleUrl: './department-table.scss', +}) +export class DepartmentTable implements OnInit { + displayedColumns: string[] = ['position', 'name', 'faculty', 'actions']; + + dataSource: DepartmentElm[] = []; + facultyMap = new Map(); + + @Input() searchText = ''; + @Input() faculty: string = ''; + + constructor( + private dialog: MatDialog, + private service: DepartmentService, + private facultyService: FacultyService + ) {} + + ngOnInit(): void { + this.loadDepartments(); + this.loadFaculties(); + + this.service.refreshNeeded.subscribe(() => { + this.loadDepartments(); + this.loadFaculties(); + }); + } + + loadDepartments(): void { + this.service.getDepartments().subscribe((data) => { + this.dataSource = data; + }); + } + + loadFaculties(): void { + this.facultyService.getFaculties().subscribe((faculties) => { + this.facultyMap = new Map(faculties.map((f) => [f.id, f.facultyName])); + }); + } + + getFacultyName(id: string): string { + return this.facultyMap.get(id) || '—'; + } + + get filteredDepartments(): DepartmentElm[] { + return this.dataSource.filter((dept) => { + const matchesFaculty = !this.faculty || dept.facultyId === this.faculty; + const matchesSearch = + !this.searchText || + dept.departmentName.toLowerCase().includes(this.searchText.toLowerCase()); + + return matchesFaculty && matchesSearch; + }); + } + + onEdit(element: DepartmentElm): void { + this.dialog.open(DepartmentEditForm, { + data: { + id: element.id, + departmentName: element.departmentName, + facultyId: element.facultyId, + }, + }); + } + + onDelete(element: DepartmentElm): void { + this.dialog.open(DepartmentDeleteForm, { + data: { + id: element.id, + departmentName: element.departmentName, + }, + }); + } + + static getFilterOptions(data: DepartmentElm[], facultyMap: Map) { + const faculties = [...new Set(data.map((e) => e.facultyId))].map((id) => ({ + id, + name: facultyMap.get(id) || '—', + })); + + return { faculties }; + } +} From 9db10f50438365b29f9eddcc77a84a291950364c Mon Sep 17 00:00:00 2001 From: Ivcho Date: Tue, 23 Jun 2026 15:51:52 +0300 Subject: [PATCH 02/17] Replaced constructor injection with inject() --- uniplanWeb/src/app/features/department/department-service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/uniplanWeb/src/app/features/department/department-service.ts b/uniplanWeb/src/app/features/department/department-service.ts index acd16d1..d832a94 100644 --- a/uniplanWeb/src/app/features/department/department-service.ts +++ b/uniplanWeb/src/app/features/department/department-service.ts @@ -1,5 +1,5 @@ import { HttpClient } from '@angular/common/http'; -import { Injectable } from '@angular/core'; +import {inject, Injectable} from '@angular/core'; import { map, Observable, Subject } from 'rxjs'; import { DepartmentElm } from '../../core/interfaces/department-elm'; @@ -11,7 +11,7 @@ export class DepartmentService { refreshNeeded = new Subject(); - constructor(private http: HttpClient) {} + private http = inject(HttpClient); getDepartments(): Observable { return this.http.get(this.apiUrl).pipe( From bae9f62d56aaff82182a8e55c32792c7ebbe2db8 Mon Sep 17 00:00:00 2001 From: Ivcho Date: Thu, 25 Jun 2026 13:45:21 +0300 Subject: [PATCH 03/17] Added i18n to department --- uniplanWeb/public/i18n/bg.json | 18 ++++++++++++++++-- uniplanWeb/public/i18n/en.json | 17 +++++++++++++++-- .../department-add-form.html | 6 +++--- .../department-add-form/department-add-form.ts | 2 ++ .../department-delete-form.html | 2 +- .../department-delete-form.ts | 3 ++- .../department-edit-form.html | 6 +++--- .../department-edit-form.ts | 2 ++ .../department-filters/department-filters.html | 4 ++-- .../department-filters/department-filters.ts | 2 ++ .../department-table/department-table.html | 6 +++--- .../department-table/department-table.ts | 3 ++- 12 files changed, 53 insertions(+), 18 deletions(-) diff --git a/uniplanWeb/public/i18n/bg.json b/uniplanWeb/public/i18n/bg.json index 0490333..3d41b18 100644 --- a/uniplanWeb/public/i18n/bg.json +++ b/uniplanWeb/public/i18n/bg.json @@ -6,7 +6,9 @@ "type-label": "Тип", "study-mode-label": "Форма на обучение", "major-label": "Специалност", - "location-label": "Местоположение" + "location-label": "Местоположение", + "department-label": "Катедра" + }, "faculty": { "delete-button": "Изтрий", @@ -33,6 +35,18 @@ "title": "Редактирай специалност" } }, + "department": { + "delete-button": "Изтрий", + "edit-button": "Редактирай", + "uni-label": "Университет", + "delete-confirmation": "Сигурни ли сте, че искате да изтриете катедра с име: ", + "add": { + "title": "Добави катедра" + }, + "edit": { + "title": "Редактирай катедра" + } + }, "student": { "faculty-number-label": "Факултетен номер", "course-label": "Курс", @@ -78,4 +92,4 @@ "logout": "Изход", "login": "Вход" } -} \ No newline at end of file +} diff --git a/uniplanWeb/public/i18n/en.json b/uniplanWeb/public/i18n/en.json index ed2b793..14a0d22 100644 --- a/uniplanWeb/public/i18n/en.json +++ b/uniplanWeb/public/i18n/en.json @@ -6,7 +6,8 @@ "type-label": "Type", "study-mode-label": "Study mode", "major-label": "Major", - "location-label": "Location" + "location-label": "Location", + "department-label": "Department" }, "faculty": { "delete-button": "Delete", @@ -33,6 +34,18 @@ "title": "Edit major" } }, + "department": { + "delete-button": "Delete", + "edit-button": "Edit", + "uni-label": "University", + "delete-confirmation": "Are you sure you want to delete the department named: ", + "add": { + "title": "Add department" + }, + "edit": { + "title": "Edit department" + } + }, "student": { "faculty-number-label": "Student ID", "course-label": "Course", @@ -78,4 +91,4 @@ "logout": "Logout", "login": "Login" } -} \ No newline at end of file +} diff --git a/uniplanWeb/src/app/features/department/department-add-form/department-add-form.html b/uniplanWeb/src/app/features/department/department-add-form/department-add-form.html index 902767d..4b7e0c7 100644 --- a/uniplanWeb/src/app/features/department/department-add-form/department-add-form.html +++ b/uniplanWeb/src/app/features/department/department-add-form/department-add-form.html @@ -1,13 +1,13 @@ - + - Име: + {{ 'features.name-label' | translate }} - Факултет: + {{ 'features.faculty-label' | translate }} @for (fac of faculties; track fac) { diff --git a/uniplanWeb/src/app/features/department/department-add-form/department-add-form.ts b/uniplanWeb/src/app/features/department/department-add-form/department-add-form.ts index 30fa2bd..ae72f6d 100644 --- a/uniplanWeb/src/app/features/department/department-add-form/department-add-form.ts +++ b/uniplanWeb/src/app/features/department/department-add-form/department-add-form.ts @@ -12,6 +12,7 @@ import { MatSelectModule } from '@angular/material/select'; import { DepartmentService } from '../department-service'; import { FacultyElm } from '../../../core/interfaces/faculty-elm'; import { FacultyService } from '../../faculty/faculty-service'; +import {TranslatePipe} from '@ngx-translate/core'; @Component({ selector: 'app-department-add-form', @@ -24,6 +25,7 @@ import { FacultyService } from '../../faculty/faculty-service'; AddForm, MatFormFieldModule, MatSelectModule, + TranslatePipe, ], templateUrl: './department-add-form.html', changeDetection: ChangeDetectionStrategy.Eager, diff --git a/uniplanWeb/src/app/features/department/department-delete-form/department-delete-form.html b/uniplanWeb/src/app/features/department/department-delete-form/department-delete-form.html index d92524f..958f186 100644 --- a/uniplanWeb/src/app/features/department/department-delete-form/department-delete-form.html +++ b/uniplanWeb/src/app/features/department/department-delete-form/department-delete-form.html @@ -1,7 +1,7 @@

- Сигурни ли сте, че искате да изтриете катедра с име: + {{'department.delete-confirmation' | translate }} {{ data.departmentName }}?

diff --git a/uniplanWeb/src/app/features/department/department-delete-form/department-delete-form.ts b/uniplanWeb/src/app/features/department/department-delete-form/department-delete-form.ts index 38cc0a7..6f9073b 100644 --- a/uniplanWeb/src/app/features/department/department-delete-form/department-delete-form.ts +++ b/uniplanWeb/src/app/features/department/department-delete-form/department-delete-form.ts @@ -6,10 +6,11 @@ import { MatDialogRef, } from '@angular/material/dialog'; import { DepartmentService } from '../department-service'; +import {TranslatePipe} from '@ngx-translate/core'; @Component({ selector: 'app-department-delete-form', - imports: [DeleteForm, MatDialogModule], + imports: [DeleteForm, MatDialogModule, TranslatePipe], templateUrl: './department-delete-form.html', changeDetection: ChangeDetectionStrategy.Eager, styleUrl: './department-delete-form.scss', diff --git a/uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.html b/uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.html index 8882efe..c0b99cc 100644 --- a/uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.html +++ b/uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.html @@ -1,12 +1,12 @@ - + - Име: + {{ 'features.name-label' | translate }} - Факултет: + {{ 'features.faculty-label' | translate }} @for (fac of faculties; track fac) { diff --git a/uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.ts b/uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.ts index df91c3d..9d534ca 100644 --- a/uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.ts +++ b/uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.ts @@ -13,6 +13,7 @@ import { FormsModule } from '@angular/forms'; import { DepartmentService } from '../department-service'; import { FacultyService } from '../../faculty/faculty-service'; import { FacultyElm } from '../../../core/interfaces/faculty-elm'; +import {TranslatePipe} from '@ngx-translate/core'; @Component({ selector: 'app-department-edit-form', @@ -25,6 +26,7 @@ import { FacultyElm } from '../../../core/interfaces/faculty-elm'; MatInputModule, MatSelectModule, MatOptionModule, + TranslatePipe, ], templateUrl: './department-edit-form.html', changeDetection: ChangeDetectionStrategy.Eager, diff --git a/uniplanWeb/src/app/features/department/department-filters/department-filters.html b/uniplanWeb/src/app/features/department/department-filters/department-filters.html index 68e3f5a..50b2ddd 100644 --- a/uniplanWeb/src/app/features/department/department-filters/department-filters.html +++ b/uniplanWeb/src/app/features/department/department-filters/department-filters.html @@ -1,11 +1,11 @@
- Катедра + {{ 'features.name-label' | translate }} - Факултет + {{ 'features.faculty-label' | translate }} - Действия + {{ 'features.actions-label' | translate }} diff --git a/uniplanWeb/src/app/features/department/department-table/department-table.ts b/uniplanWeb/src/app/features/department/department-table/department-table.ts index 35a4ddd..e135d5e 100644 --- a/uniplanWeb/src/app/features/department/department-table/department-table.ts +++ b/uniplanWeb/src/app/features/department/department-table/department-table.ts @@ -8,10 +8,11 @@ import { MatDialog } from '@angular/material/dialog'; import { DepartmentDeleteForm } from '../department-delete-form/department-delete-form'; import { DepartmentService } from '../department-service'; import { FacultyService } from '../../faculty/faculty-service'; +import {TranslatePipe} from '@ngx-translate/core'; @Component({ selector: 'app-department-table', - imports: [MatTableModule, MatIconModule, MatButtonModule], + imports: [MatTableModule, MatIconModule, MatButtonModule, TranslatePipe], templateUrl: './department-table.html', changeDetection: ChangeDetectionStrategy.Eager, styleUrl: './department-table.scss', From a42ebbe7a3f95d2f2334f870c39b0621d4399c20 Mon Sep 17 00:00:00 2001 From: Ivcho Date: Thu, 25 Jun 2026 18:00:08 +0300 Subject: [PATCH 04/17] Fixed department compatability with routing --- uniplanWeb/src/app/app.routes.ts | 5 ++ .../navmenu-component/navmenu-component.html | 4 +- .../department-panel/department-panel.html | 16 +++++ .../department-panel/department-panel.scss | 2 + .../department-panel/department-panel.ts | 65 +++++++++++++++++++ 5 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 uniplanWeb/src/app/features/department/department-panel/department-panel.html create mode 100644 uniplanWeb/src/app/features/department/department-panel/department-panel.scss create mode 100644 uniplanWeb/src/app/features/department/department-panel/department-panel.ts diff --git a/uniplanWeb/src/app/app.routes.ts b/uniplanWeb/src/app/app.routes.ts index 60c5960..7d2ef1a 100644 --- a/uniplanWeb/src/app/app.routes.ts +++ b/uniplanWeb/src/app/app.routes.ts @@ -17,6 +17,11 @@ export const routes: Routes = [ loadComponent: () => import('./features/faculty/faculty-panel/faculty-panel').then(m => m.FacultyPanel), }, + { + path: 'department', + loadComponent: () => + import('./features/department/department-panel/department-panel').then(m => m.DepartmentPanel), + }, { path: 'major', loadComponent: () => diff --git a/uniplanWeb/src/app/core/shared/navmenu-component/navmenu-component.html b/uniplanWeb/src/app/core/shared/navmenu-component/navmenu-component.html index 781afb7..f30045b 100644 --- a/uniplanWeb/src/app/core/shared/navmenu-component/navmenu-component.html +++ b/uniplanWeb/src/app/core/shared/navmenu-component/navmenu-component.html @@ -30,7 +30,9 @@

Logo

  • - {{ 'navmenu.department' | translate }} + + {{ 'navmenu.department' | translate }} +
  • diff --git a/uniplanWeb/src/app/features/department/department-panel/department-panel.html b/uniplanWeb/src/app/features/department/department-panel/department-panel.html new file mode 100644 index 0000000..466caeb --- /dev/null +++ b/uniplanWeb/src/app/features/department/department-panel/department-panel.html @@ -0,0 +1,16 @@ +
    +
    + + + +
    + + +
    diff --git a/uniplanWeb/src/app/features/department/department-panel/department-panel.scss b/uniplanWeb/src/app/features/department/department-panel/department-panel.scss new file mode 100644 index 0000000..2d29f1b --- /dev/null +++ b/uniplanWeb/src/app/features/department/department-panel/department-panel.scss @@ -0,0 +1,2 @@ +@use '../../../../styles/panel' as *; +@include panel-styles(); diff --git a/uniplanWeb/src/app/features/department/department-panel/department-panel.ts b/uniplanWeb/src/app/features/department/department-panel/department-panel.ts new file mode 100644 index 0000000..8237fd1 --- /dev/null +++ b/uniplanWeb/src/app/features/department/department-panel/department-panel.ts @@ -0,0 +1,65 @@ +import { Component, DestroyRef, OnInit, inject } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { catchError, combineLatest, map, merge, of, switchMap } from 'rxjs'; + +import { DepartmentElm } from '../../../core/interfaces/department-elm'; +import { FacultyService } from '../../faculty/faculty-service'; +import { DepartmentOptions } from '../department-options/department-options'; +import { DepartmentFilters } from '../department-filters/department-filters'; +import { DepartmentTable } from '../department-table/department-table'; +import { DepartmentService } from '../department-service'; + +@Component({ + selector: 'app-department-panel', + imports: [DepartmentOptions, DepartmentFilters, DepartmentTable], + templateUrl: './department-panel.html', + styleUrl: './department-panel.scss', +}) +export class DepartmentPanel implements OnInit { + searchText: string = ''; + selectedFaculty: string = ''; + + faculties: { id: string; name: string }[] = []; + + private departments: DepartmentElm[] = []; + private facultyMap: Map = new Map(); + private destroyRef: DestroyRef = inject(DestroyRef); + + constructor( + private departmentService: DepartmentService, + private facultyService: FacultyService, + ) {} + + ngOnInit(): void { + const facultyMap$ = merge(of(undefined), this.facultyService.refreshNeeded).pipe( + switchMap(() => + this.facultyService.getFaculties().pipe( + map((faculties) => new Map(faculties.map((f) => [f.id, f.facultyName]))), + catchError(() => of(this.facultyMap)), + ), + ), + ); + + const departments$ = merge(of(undefined), this.departmentService.refreshNeeded).pipe( + switchMap(() => + this.departmentService.getDepartments().pipe( + catchError(() => of(this.departments)), + ), + ), + ); + + combineLatest([facultyMap$, departments$]) + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe(([facultyMap, departments]) => { + this.facultyMap = facultyMap; + this.departments = departments; + + const options = DepartmentTable.getFilterOptions(this.departments, this.facultyMap); + this.faculties = options.faculties; + + if (this.selectedFaculty && !this.faculties.some((f) => f.id === this.selectedFaculty)) { + this.selectedFaculty = ''; + } + }); + } +} From cd6e6f44fb8247a43dd62494c1a130fbe52b1704 Mon Sep 17 00:00:00 2001 From: Ivcho Date: Mon, 29 Jun 2026 11:08:30 +0300 Subject: [PATCH 05/17] Comments resolved, used reactive forms, added access modifiers and types, added translate pipes to hardcoded alerts --- uniplanWeb/public/i18n/bg.json | 9 +++- uniplanWeb/public/i18n/en.json | 9 +++- .../department-add-form.html | 12 +++-- .../department-add-form.ts | 42 +++++++++++------ .../department-delete-form.ts | 22 ++++----- .../department-edit-form.html | 36 +++++++++------ .../department-edit-form.ts | 46 +++++++++++++------ .../department-filters.html | 6 +-- .../department-filters/department-filters.ts | 12 ++--- .../department-options/department-options.ts | 2 +- .../features/department/department-service.ts | 12 ++--- .../department-table/department-table.html | 2 +- .../department-table/department-table.ts | 32 +++++++------ 13 files changed, 153 insertions(+), 89 deletions(-) diff --git a/uniplanWeb/public/i18n/bg.json b/uniplanWeb/public/i18n/bg.json index 3d41b18..594cea2 100644 --- a/uniplanWeb/public/i18n/bg.json +++ b/uniplanWeb/public/i18n/bg.json @@ -7,7 +7,8 @@ "study-mode-label": "Форма на обучение", "major-label": "Специалност", "location-label": "Местоположение", - "department-label": "Катедра" + "department-label": "Катедра", + "number-label": "№" }, "faculty": { @@ -40,6 +41,9 @@ "edit-button": "Редактирай", "uni-label": "Университет", "delete-confirmation": "Сигурни ли сте, че искате да изтриете катедра с име: ", + "create-error": "Възникна грешка при създаването на катедрата.", + "update-error": "Възникна грешка при редактирането на катедрата.", + "delete-error": "Възникна грешка при изтриването на катедрата.", "add": { "title": "Добави катедра" }, @@ -67,7 +71,8 @@ "save-button": "Запази", "delete-button": "Изтрий", "option-all": "Всички", - "confirm-delete": "Потвърдете изтриването" + "confirm-delete": "Потвърдете изтриването", + "required-field": "Това поле е задължително" }, "home": { "title": "Начало!" diff --git a/uniplanWeb/public/i18n/en.json b/uniplanWeb/public/i18n/en.json index 14a0d22..517d38b 100644 --- a/uniplanWeb/public/i18n/en.json +++ b/uniplanWeb/public/i18n/en.json @@ -7,7 +7,8 @@ "study-mode-label": "Study mode", "major-label": "Major", "location-label": "Location", - "department-label": "Department" + "department-label": "Department", + "number-label": "No." }, "faculty": { "delete-button": "Delete", @@ -39,6 +40,9 @@ "edit-button": "Edit", "uni-label": "University", "delete-confirmation": "Are you sure you want to delete the department named: ", + "create-error": "Failed to create department.", + "update-error": "Failed to update department.", + "delete-error": "Failed to delete department.", "add": { "title": "Add department" }, @@ -66,7 +70,8 @@ "save-button": "Save", "delete-button": "Delete", "option-all": "All", - "confirm-delete": "Confirm deletion" + "confirm-delete": "Confirm deletion", + "required-field": "This field is required" }, "home": { "title": "Main!" diff --git a/uniplanWeb/src/app/features/department/department-add-form/department-add-form.html b/uniplanWeb/src/app/features/department/department-add-form/department-add-form.html index 4b7e0c7..f387e91 100644 --- a/uniplanWeb/src/app/features/department/department-add-form/department-add-form.html +++ b/uniplanWeb/src/app/features/department/department-add-form/department-add-form.html @@ -1,20 +1,26 @@ - + {{ 'features.name-label' | translate }} - + + @if (form.controls.departmentName.hasError('required')) { + {{ 'shared.required-field' | translate }} + } {{ 'features.faculty-label' | translate }} - + @for (fac of faculties; track fac) { {{ fac.facultyName }} } + @if (form.controls.facultyId.hasError('required')) { + {{ 'shared.required-field' | translate }} + } diff --git a/uniplanWeb/src/app/features/department/department-add-form/department-add-form.ts b/uniplanWeb/src/app/features/department/department-add-form/department-add-form.ts index ae72f6d..722596d 100644 --- a/uniplanWeb/src/app/features/department/department-add-form/department-add-form.ts +++ b/uniplanWeb/src/app/features/department/department-add-form/department-add-form.ts @@ -6,13 +6,18 @@ import { MatFormFieldModule, MatLabel, } from '@angular/material/form-field'; -import { FormsModule } from '@angular/forms'; +import { + FormControl, + FormGroup, + ReactiveFormsModule, + Validators, +} from '@angular/forms'; import { MatInputModule } from '@angular/material/input'; import { MatSelectModule } from '@angular/material/select'; import { DepartmentService } from '../department-service'; import { FacultyElm } from '../../../core/interfaces/faculty-elm'; import { FacultyService } from '../../faculty/faculty-service'; -import {TranslatePipe} from '@ngx-translate/core'; +import { TranslatePipe, TranslateService } from '@ngx-translate/core'; @Component({ selector: 'app-department-add-form', @@ -20,7 +25,7 @@ import {TranslatePipe} from '@ngx-translate/core'; MatDialogModule, MatFormField, MatLabel, - FormsModule, + ReactiveFormsModule, MatInputModule, AddForm, MatFormFieldModule, @@ -32,15 +37,24 @@ import {TranslatePipe} from '@ngx-translate/core'; styleUrl: './department-add-form.scss', }) export class DepartmentAddForm implements OnInit { - departmentName = ''; - facultyId = ''; + protected readonly form = new FormGroup({ + departmentName: new FormControl('', { + nonNullable: true, + validators: [Validators.required], + }), + facultyId: new FormControl('', { + nonNullable: true, + validators: [Validators.required], + }), + }); - faculties: FacultyElm[] = []; + protected faculties: FacultyElm[] = []; constructor( private dialogRef: MatDialogRef, private departmentService: DepartmentService, - private facultyService: FacultyService + private facultyService: FacultyService, + private translate: TranslateService ) {} ngOnInit(): void { @@ -52,22 +66,24 @@ export class DepartmentAddForm implements OnInit { }); } - save() { - if (!this.departmentName.trim() || !this.facultyId) { - alert('Please fill all fields.'); + protected save(): void { + if (this.form.invalid) { + this.form.markAllAsTouched(); return; } + const { departmentName, facultyId } = this.form.getRawValue(); + this.departmentService .createDepartment({ - departmentName: this.departmentName, - facultyId: this.facultyId, + departmentName: departmentName.trim(), + facultyId, }) .subscribe({ next: () => { this.dialogRef.close(true); }, - error: () => alert('Failed to create department.'), + error: () => alert(this.translate.instant('department.create-error')), }); } } diff --git a/uniplanWeb/src/app/features/department/department-delete-form/department-delete-form.ts b/uniplanWeb/src/app/features/department/department-delete-form/department-delete-form.ts index 6f9073b..2376cd0 100644 --- a/uniplanWeb/src/app/features/department/department-delete-form/department-delete-form.ts +++ b/uniplanWeb/src/app/features/department/department-delete-form/department-delete-form.ts @@ -1,12 +1,8 @@ -import { Component, Inject, ChangeDetectionStrategy } from '@angular/core'; -import { DeleteForm } from '../../../core/shared/delete-form/delete-form'; -import { - MAT_DIALOG_DATA, - MatDialogModule, - MatDialogRef, -} from '@angular/material/dialog'; -import { DepartmentService } from '../department-service'; -import {TranslatePipe} from '@ngx-translate/core'; +import {ChangeDetectionStrategy, Component, Inject} from '@angular/core'; +import {DeleteForm} from '../../../core/shared/delete-form/delete-form'; +import {MAT_DIALOG_DATA, MatDialogModule, MatDialogRef,} from '@angular/material/dialog'; +import {DepartmentService} from '../department-service'; +import {TranslatePipe, TranslateService} from '@ngx-translate/core'; @Component({ selector: 'app-department-delete-form', @@ -19,11 +15,13 @@ export class DepartmentDeleteForm { constructor( private departmentService: DepartmentService, private dialogRef: MatDialogRef, + private translate: TranslateService, @Inject(MAT_DIALOG_DATA) public data: { id: string; departmentName: string } - ) {} + ) { + } - deleteDepartment(): void { + protected deleteDepartment(): void { this.departmentService .deleteDepartment(this.data.id) .subscribe({ @@ -31,7 +29,7 @@ export class DepartmentDeleteForm { this.dialogRef.close(true); }, error: () => { - alert('Възникна грешка при изтриването на катедрата.'); + alert(this.translate.instant('department.delete-error')); }, }); } diff --git a/uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.html b/uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.html index c0b99cc..01aa3c1 100644 --- a/uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.html +++ b/uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.html @@ -1,19 +1,29 @@ - - {{ 'features.name-label' | translate }} - - +
    - - {{ 'features.faculty-label' | translate }} - - @for (fac of faculties; track fac) { - - {{ fac.facultyName }} - + + {{ 'features.name-label' | translate }} + + @if (form.controls.departmentName.hasError('required')) { + {{ 'shared.required-field' | translate }} } - - + + + + {{ 'features.faculty-label' | translate }} + + @for (fac of faculties; track fac) { + + {{ fac.facultyName }} + + } + + @if (form.controls.facultyId.hasError('required')) { + {{ 'shared.required-field' | translate }} + } + + +
    diff --git a/uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.ts b/uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.ts index 9d534ca..536a123 100644 --- a/uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.ts +++ b/uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.ts @@ -9,11 +9,16 @@ import { import { MatOptionModule } from '@angular/material/core'; import { MatSelectModule } from '@angular/material/select'; import { MatInputModule } from '@angular/material/input'; -import { FormsModule } from '@angular/forms'; +import { + FormControl, + FormGroup, + ReactiveFormsModule, + Validators, +} from '@angular/forms'; import { DepartmentService } from '../department-service'; import { FacultyService } from '../../faculty/faculty-service'; import { FacultyElm } from '../../../core/interfaces/faculty-elm'; -import {TranslatePipe} from '@ngx-translate/core'; +import { TranslatePipe, TranslateService } from '@ngx-translate/core'; @Component({ selector: 'app-department-edit-form', @@ -22,7 +27,7 @@ import {TranslatePipe} from '@ngx-translate/core'; MatDialogModule, MatFormField, MatLabel, - FormsModule, + ReactiveFormsModule, MatInputModule, MatSelectModule, MatOptionModule, @@ -33,15 +38,18 @@ import {TranslatePipe} from '@ngx-translate/core'; styleUrl: './department-edit-form.scss', }) export class DepartmentEditForm implements OnInit { - departmentName = ''; - facultyId = ''; + protected readonly form: FormGroup<{ + departmentName: FormControl; + facultyId: FormControl; + }>; - faculties: FacultyElm[] = []; + protected faculties: FacultyElm[] = []; constructor( private dialogRef: MatDialogRef, private departmentService: DepartmentService, private facultyService: FacultyService, + private translate: TranslateService, @Inject(MAT_DIALOG_DATA) public data: { id: string; @@ -49,8 +57,16 @@ export class DepartmentEditForm implements OnInit { facultyId?: string; } ) { - this.departmentName = data.departmentName; - this.facultyId = data.facultyId || ''; + this.form = new FormGroup({ + departmentName: new FormControl(data.departmentName ?? '', { + nonNullable: true, + validators: [Validators.required], + }), + facultyId: new FormControl(data.facultyId ?? '', { + nonNullable: true, + validators: [Validators.required], + }), + }); } ngOnInit(): void { @@ -60,20 +76,22 @@ export class DepartmentEditForm implements OnInit { }); } - save() { - if (!this.departmentName.trim()) { - alert('Please enter the department name.'); + protected save(): void { + if (this.form.invalid) { + this.form.markAllAsTouched(); return; } + const { departmentName, facultyId } = this.form.getRawValue(); + this.departmentService .editDepartment(this.data.id, { - departmentName: this.departmentName, - facultyId: this.facultyId, + departmentName: departmentName.trim(), + facultyId, }) .subscribe({ next: () => this.dialogRef.close(true), - error: () => alert('Failed to update department.'), + error: () => alert(this.translate.instant('department.update-error')), }); } } diff --git a/uniplanWeb/src/app/features/department/department-filters/department-filters.html b/uniplanWeb/src/app/features/department/department-filters/department-filters.html index 50b2ddd..d59383e 100644 --- a/uniplanWeb/src/app/features/department/department-filters/department-filters.html +++ b/uniplanWeb/src/app/features/department/department-filters/department-filters.html @@ -1,13 +1,13 @@
    diff --git a/uniplanWeb/src/app/features/department/department-filters/department-filters.ts b/uniplanWeb/src/app/features/department/department-filters/department-filters.ts index aea5e91..c66ae38 100644 --- a/uniplanWeb/src/app/features/department/department-filters/department-filters.ts +++ b/uniplanWeb/src/app/features/department/department-filters/department-filters.ts @@ -1,4 +1,4 @@ -import { Component, EventEmitter, Input, Output, ChangeDetectionStrategy } from '@angular/core'; +import { Component, ChangeDetectionStrategy, input, output } from '@angular/core'; import { FiltersForm } from '../../../core/shared/filters-form/filters-form'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; @@ -21,12 +21,12 @@ import {TranslatePipe} from '@ngx-translate/core'; styleUrl: './department-filters.scss', }) export class DepartmentFilters { - @Input() internalSearchText = ''; + readonly internalSearchText = input(''); - @Input() faculties: { id: string; name: string }[] = []; + readonly faculties = input<{ id: string; name: string }[]>([]); - @Input() selectedFaculty = ''; + readonly selectedFaculty = input(''); - @Output() facultyChange = new EventEmitter(); - @Output() searchTextChange = new EventEmitter(); + readonly facultyChange = output(); + readonly searchTextChange = output(); } diff --git a/uniplanWeb/src/app/features/department/department-options/department-options.ts b/uniplanWeb/src/app/features/department/department-options/department-options.ts index a69a1aa..ad086f1 100644 --- a/uniplanWeb/src/app/features/department/department-options/department-options.ts +++ b/uniplanWeb/src/app/features/department/department-options/department-options.ts @@ -21,7 +21,7 @@ import { AddButton } from '../../../core/shared/add-button/add-button'; export class DepartmentOptions { constructor(private dialog: MatDialog) {} - openAddForm() { + protected openAddForm(): void { this.dialog.open(DepartmentAddForm, { width: '400px', }); diff --git a/uniplanWeb/src/app/features/department/department-service.ts b/uniplanWeb/src/app/features/department/department-service.ts index d832a94..e27f536 100644 --- a/uniplanWeb/src/app/features/department/department-service.ts +++ b/uniplanWeb/src/app/features/department/department-service.ts @@ -29,8 +29,8 @@ export class DepartmentService { createDepartment(data: { departmentName: string; facultyId: string; - }): Observable { - return this.http.post(`${this.apiUrl}`, data).pipe( + }): Observable { + return this.http.post(`${this.apiUrl}`, data).pipe( map((res) => { this.refreshNeeded.next(); return res; @@ -41,8 +41,8 @@ export class DepartmentService { editDepartment( id: string, data: { departmentName: string; facultyId: string } - ): Observable { - return this.http.put(`${this.apiUrl}/${id}`, data).pipe( + ): Observable { + return this.http.put(`${this.apiUrl}/${id}`, data).pipe( map((res) => { this.refreshNeeded.next(); return res; @@ -50,8 +50,8 @@ export class DepartmentService { ); } - deleteDepartment(id: string): Observable { - return this.http.delete(`${this.apiUrl}/${id}`).pipe( + deleteDepartment(id: string): Observable { + return this.http.delete(`${this.apiUrl}/${id}`).pipe( map((res) => { this.refreshNeeded.next(); return res; diff --git a/uniplanWeb/src/app/features/department/department-table/department-table.html b/uniplanWeb/src/app/features/department/department-table/department-table.html index 23923ec..5a13a35 100644 --- a/uniplanWeb/src/app/features/department/department-table/department-table.html +++ b/uniplanWeb/src/app/features/department/department-table/department-table.html @@ -2,7 +2,7 @@ - diff --git a/uniplanWeb/src/app/features/department/department-table/department-table.ts b/uniplanWeb/src/app/features/department/department-table/department-table.ts index e135d5e..fad38e0 100644 --- a/uniplanWeb/src/app/features/department/department-table/department-table.ts +++ b/uniplanWeb/src/app/features/department/department-table/department-table.ts @@ -1,4 +1,4 @@ -import { Component, Input, OnInit, ChangeDetectionStrategy } from '@angular/core'; +import { Component, OnInit, ChangeDetectionStrategy, input } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; import { MatTableModule } from '@angular/material/table'; @@ -23,8 +23,8 @@ export class DepartmentTable implements OnInit { dataSource: DepartmentElm[] = []; facultyMap = new Map(); - @Input() searchText = ''; - @Input() faculty: string = ''; + readonly searchText = input(''); + readonly faculty = input(''); constructor( private dialog: MatDialog, @@ -42,34 +42,37 @@ export class DepartmentTable implements OnInit { }); } - loadDepartments(): void { + private loadDepartments(): void { this.service.getDepartments().subscribe((data) => { this.dataSource = data; }); } - loadFaculties(): void { + private loadFaculties(): void { this.facultyService.getFaculties().subscribe((faculties) => { this.facultyMap = new Map(faculties.map((f) => [f.id, f.facultyName])); }); } - getFacultyName(id: string): string { + protected getFacultyName(id: string): string { return this.facultyMap.get(id) || '—'; } - get filteredDepartments(): DepartmentElm[] { + protected get filteredDepartments(): DepartmentElm[] { + const faculty = this.faculty(); + const searchText = this.searchText(); + return this.dataSource.filter((dept) => { - const matchesFaculty = !this.faculty || dept.facultyId === this.faculty; + const matchesFaculty = !faculty || dept.facultyId === faculty; const matchesSearch = - !this.searchText || - dept.departmentName.toLowerCase().includes(this.searchText.toLowerCase()); + !searchText || + dept.departmentName.toLowerCase().includes(searchText.toLowerCase()); return matchesFaculty && matchesSearch; }); } - onEdit(element: DepartmentElm): void { + protected onEdit(element: DepartmentElm): void { this.dialog.open(DepartmentEditForm, { data: { id: element.id, @@ -79,7 +82,7 @@ export class DepartmentTable implements OnInit { }); } - onDelete(element: DepartmentElm): void { + protected onDelete(element: DepartmentElm): void { this.dialog.open(DepartmentDeleteForm, { data: { id: element.id, @@ -88,7 +91,10 @@ export class DepartmentTable implements OnInit { }); } - static getFilterOptions(data: DepartmentElm[], facultyMap: Map) { + static getFilterOptions( + data: DepartmentElm[], + facultyMap: Map, + ): { faculties: { id: string; name: string }[] } { const faculties = [...new Set(data.map((e) => e.facultyId))].map((id) => ({ id, name: facultyMap.get(id) || '—', From f9a04e57d838dd0f2f7ce52688c62b7633853c2d Mon Sep 17 00:00:00 2001 From: Ivcho Date: Tue, 30 Jun 2026 15:06:25 +0300 Subject: [PATCH 06/17] Fixed compatability with extracted URLs --- uniplanWeb/src/app/config/endpoints.ts | 9 +++++++++ uniplanWeb/src/environments/environment.ts | 4 ++++ 2 files changed, 13 insertions(+) create mode 100644 uniplanWeb/src/app/config/endpoints.ts create mode 100644 uniplanWeb/src/environments/environment.ts diff --git a/uniplanWeb/src/app/config/endpoints.ts b/uniplanWeb/src/app/config/endpoints.ts new file mode 100644 index 0000000..d11935e --- /dev/null +++ b/uniplanWeb/src/app/config/endpoints.ts @@ -0,0 +1,9 @@ +import { environment } from '../../environments/environment'; + +export const API_ENDPOINTS = { + universities: `${environment.baseUrl}/universities`, + courses: `${environment.baseUrl}/courses`, + faculties: `${environment.baseUrl}/faculties`, + majors: `${environment.baseUrl}/majors`, + departments: `${environment.baseUrl}/departments`, +}; diff --git a/uniplanWeb/src/environments/environment.ts b/uniplanWeb/src/environments/environment.ts new file mode 100644 index 0000000..2cda654 --- /dev/null +++ b/uniplanWeb/src/environments/environment.ts @@ -0,0 +1,4 @@ +export const environment = { + production: false, + baseUrl: 'http://localhost:8080/api' +}; From caacbe1f323ba7fd3291ad52446833d67cfb2700 Mon Sep 17 00:00:00 2001 From: Ivcho Date: Tue, 30 Jun 2026 15:24:50 +0300 Subject: [PATCH 07/17] Refactored services to use the extracted URLs properly --- .../features/department/department-service.ts | 11 +++++------ .../src/app/features/faculty/faculty-service.ts | 11 +++++------ .../src/app/features/major/major-service.ts | 17 +++++++---------- .../features/university/university-service.ts | 5 ++--- 4 files changed, 19 insertions(+), 25 deletions(-) diff --git a/uniplanWeb/src/app/features/department/department-service.ts b/uniplanWeb/src/app/features/department/department-service.ts index e27f536..982d138 100644 --- a/uniplanWeb/src/app/features/department/department-service.ts +++ b/uniplanWeb/src/app/features/department/department-service.ts @@ -2,19 +2,18 @@ import { HttpClient } from '@angular/common/http'; import {inject, Injectable} from '@angular/core'; import { map, Observable, Subject } from 'rxjs'; import { DepartmentElm } from '../../core/interfaces/department-elm'; +import { API_ENDPOINTS } from '../../config/endpoints'; @Injectable({ providedIn: 'root', }) export class DepartmentService { - private apiUrl = 'http://localhost:8080/api/departments'; - refreshNeeded = new Subject(); private http = inject(HttpClient); getDepartments(): Observable { - return this.http.get(this.apiUrl).pipe( + return this.http.get(API_ENDPOINTS.departments).pipe( map((departments) => departments.map((dept, index) => ({ id: dept.id, @@ -30,7 +29,7 @@ export class DepartmentService { departmentName: string; facultyId: string; }): Observable { - return this.http.post(`${this.apiUrl}`, data).pipe( + return this.http.post(`${API_ENDPOINTS.departments}`, data).pipe( map((res) => { this.refreshNeeded.next(); return res; @@ -42,7 +41,7 @@ export class DepartmentService { id: string, data: { departmentName: string; facultyId: string } ): Observable { - return this.http.put(`${this.apiUrl}/${id}`, data).pipe( + return this.http.put(`${API_ENDPOINTS.departments}/${id}`, data).pipe( map((res) => { this.refreshNeeded.next(); return res; @@ -51,7 +50,7 @@ export class DepartmentService { } deleteDepartment(id: string): Observable { - return this.http.delete(`${this.apiUrl}/${id}`).pipe( + return this.http.delete(`${API_ENDPOINTS.departments}/${id}`).pipe( map((res) => { this.refreshNeeded.next(); return res; diff --git a/uniplanWeb/src/app/features/faculty/faculty-service.ts b/uniplanWeb/src/app/features/faculty/faculty-service.ts index 506fe3e..65fdb1a 100644 --- a/uniplanWeb/src/app/features/faculty/faculty-service.ts +++ b/uniplanWeb/src/app/features/faculty/faculty-service.ts @@ -2,19 +2,18 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { map, Observable, Subject } from 'rxjs'; import { FacultyElm } from '../../core/interfaces/faculty-elm'; +import { API_ENDPOINTS } from '../../config/endpoints'; @Injectable({ providedIn: 'root', }) export class FacultyService { - private apiUrl = 'http://localhost:8080/api/faculties'; - refreshNeeded = new Subject(); constructor(private http: HttpClient) {} getFaculties(): Observable { - return this.http.get(this.apiUrl).pipe( + return this.http.get(API_ENDPOINTS.faculties).pipe( map((faculties) => faculties.map((faculty, index) => ({ id: faculty.id, @@ -32,7 +31,7 @@ export class FacultyService { facultyName: string; location: string; }): Observable { - return this.http.post(`${this.apiUrl}`, faculty).pipe( + return this.http.post(`${API_ENDPOINTS.faculties}`, faculty).pipe( map((res) => { this.refreshNeeded.next(); return res; @@ -48,7 +47,7 @@ export class FacultyService { location: string; } ): Observable { - return this.http.put(`${this.apiUrl}/${id}`, updatedFaculty).pipe( + return this.http.put(`${API_ENDPOINTS.faculties}/${id}`, updatedFaculty).pipe( map((res) => { this.refreshNeeded.next(); return res; @@ -57,7 +56,7 @@ export class FacultyService { } deleteFaculty(id: string): Observable { - return this.http.delete(`${this.apiUrl}/${id}`).pipe( + return this.http.delete(`${API_ENDPOINTS.faculties}/${id}`).pipe( map((res) => { this.refreshNeeded.next(); return res; diff --git a/uniplanWeb/src/app/features/major/major-service.ts b/uniplanWeb/src/app/features/major/major-service.ts index f1e0d89..f65d6c4 100644 --- a/uniplanWeb/src/app/features/major/major-service.ts +++ b/uniplanWeb/src/app/features/major/major-service.ts @@ -2,21 +2,18 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { map, Observable, Subject, switchMap } from 'rxjs'; import { MajorElm } from '../../core/interfaces/major-elm'; +import { API_ENDPOINTS } from '../../config/endpoints'; @Injectable({ providedIn: 'root', }) export class MajorService { - private apiUrl = 'http://localhost:8080/api/majors'; - private apiUrlMajor = 'http://localhost:8080/api/majors'; - private apiUrlCourse = 'http://localhost:8080/api/courses'; - refreshNeeded = new Subject(); constructor(private http: HttpClient) {} getMajors(): Observable { - return this.http.get(this.apiUrl).pipe( + return this.http.get(API_ENDPOINTS.majors).pipe( map((majors) => majors.map((major, index) => ({ id: major.id, @@ -35,7 +32,7 @@ export class MajorService { facultyId: string; majorName: string; }): Observable { - return this.http.post(`${this.apiUrlMajor}`, createMajor).pipe( + return this.http.post(`${API_ENDPOINTS.majors}`, createMajor).pipe( map((res) => { this.refreshNeeded.next(); return res; @@ -49,7 +46,7 @@ export class MajorService { courseType: string; courseSubtype: string; }): Observable { - return this.http.post(`${this.apiUrlCourse}`, course).pipe( + return this.http.post(`${API_ENDPOINTS.courses}`, course).pipe( map((res) => { this.refreshNeeded.next(); return res; @@ -78,7 +75,7 @@ export class MajorService { } deleteMajor(id: string): Observable { - return this.http.delete(`${this.apiUrlMajor}/${id}`).pipe( + return this.http.delete(`${API_ENDPOINTS.majors}/${id}`).pipe( map((res) => { this.refreshNeeded.next(); return res; @@ -87,7 +84,7 @@ export class MajorService { } deleteCourse(courseId: string): Observable { - return this.http.delete(`${this.apiUrlCourse}/${courseId}`); + return this.http.delete(`${API_ENDPOINTS.courses}/${courseId}`); } deleteMajorWithCourse(major: MajorElm): Observable { @@ -100,7 +97,7 @@ export class MajorService { id: string, updateMajor: { facultyId: string; majorName: string } ): Observable { - return this.http.put(`${this.apiUrlMajor}/${id}`, updateMajor).pipe( + return this.http.put(`${API_ENDPOINTS.majors}/${id}`, updateMajor).pipe( map((res) => { this.refreshNeeded.next(); return res; diff --git a/uniplanWeb/src/app/features/university/university-service.ts b/uniplanWeb/src/app/features/university/university-service.ts index 7516f0a..32f24de 100644 --- a/uniplanWeb/src/app/features/university/university-service.ts +++ b/uniplanWeb/src/app/features/university/university-service.ts @@ -1,6 +1,7 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; +import { API_ENDPOINTS } from '../../config/endpoints'; export interface UniversityElm { id: string; @@ -15,11 +16,9 @@ export interface UniversityElm { providedIn: 'root', }) export class UniversityService { -private apiUrl = 'http://localhost:8080/api/universities'; - constructor(private http: HttpClient) {} getAllUniversities(): Observable { - return this.http.get(this.apiUrl); + return this.http.get(API_ENDPOINTS.universities); } } From b28ac46f287618d9f676f59cdd16b8412d4ef515 Mon Sep 17 00:00:00 2001 From: Ivcho Date: Tue, 23 Jun 2026 13:47:56 +0300 Subject: [PATCH 08/17] Implemented fully working department view # Conflicts: # uniplanWeb/src/app/core/shared/main-panel/main-panel.html # uniplanWeb/src/app/core/shared/main-panel/main-panel.ts # uniplanWeb/src/app/core/shared/navmenu-component/navmenu-component.html # uniplanWeb/src/app/core/shared/navmenu-component/navmenu-component.ts --- .../src/app/core/interfaces/department-elm.ts | 6 ++ .../department-add-form.html | 21 ++++ .../department-add-form.scss | 14 +++ .../department-add-form.spec.ts | 18 ++++ .../department-add-form.ts | 71 ++++++++++++++ .../department-delete-form.html | 8 ++ .../department-delete-form.scss | 0 .../department-delete-form.spec.ts | 18 ++++ .../department-delete-form.ts | 37 +++++++ .../department-edit-form.html | 19 ++++ .../department-edit-form.scss | 13 +++ .../department-edit-form.spec.ts | 18 ++++ .../department-edit-form.ts | 77 +++++++++++++++ .../department-filters.html | 14 +++ .../department-filters.scss | 14 +++ .../department-filters.spec.ts | 18 ++++ .../department-filters/department-filters.ts | 30 ++++++ .../department-options.html | 2 + .../department-options.scss | 3 + .../department-options.spec.ts | 18 ++++ .../department-options/department-options.ts | 29 ++++++ .../department/department-service.spec.ts | 12 +++ .../features/department/department-service.ts | 61 ++++++++++++ .../department-table/department-table.html | 61 ++++++++++++ .../department-table/department-table.scss | 74 ++++++++++++++ .../department-table/department-table.spec.ts | 18 ++++ .../department-table/department-table.ts | 98 +++++++++++++++++++ 27 files changed, 772 insertions(+) create mode 100644 uniplanWeb/src/app/core/interfaces/department-elm.ts create mode 100644 uniplanWeb/src/app/features/department/department-add-form/department-add-form.html create mode 100644 uniplanWeb/src/app/features/department/department-add-form/department-add-form.scss create mode 100644 uniplanWeb/src/app/features/department/department-add-form/department-add-form.spec.ts create mode 100644 uniplanWeb/src/app/features/department/department-add-form/department-add-form.ts create mode 100644 uniplanWeb/src/app/features/department/department-delete-form/department-delete-form.html create mode 100644 uniplanWeb/src/app/features/department/department-delete-form/department-delete-form.scss create mode 100644 uniplanWeb/src/app/features/department/department-delete-form/department-delete-form.spec.ts create mode 100644 uniplanWeb/src/app/features/department/department-delete-form/department-delete-form.ts create mode 100644 uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.html create mode 100644 uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.scss create mode 100644 uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.spec.ts create mode 100644 uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.ts create mode 100644 uniplanWeb/src/app/features/department/department-filters/department-filters.html create mode 100644 uniplanWeb/src/app/features/department/department-filters/department-filters.scss create mode 100644 uniplanWeb/src/app/features/department/department-filters/department-filters.spec.ts create mode 100644 uniplanWeb/src/app/features/department/department-filters/department-filters.ts create mode 100644 uniplanWeb/src/app/features/department/department-options/department-options.html create mode 100644 uniplanWeb/src/app/features/department/department-options/department-options.scss create mode 100644 uniplanWeb/src/app/features/department/department-options/department-options.spec.ts create mode 100644 uniplanWeb/src/app/features/department/department-options/department-options.ts create mode 100644 uniplanWeb/src/app/features/department/department-service.spec.ts create mode 100644 uniplanWeb/src/app/features/department/department-service.ts create mode 100644 uniplanWeb/src/app/features/department/department-table/department-table.html create mode 100644 uniplanWeb/src/app/features/department/department-table/department-table.scss create mode 100644 uniplanWeb/src/app/features/department/department-table/department-table.spec.ts create mode 100644 uniplanWeb/src/app/features/department/department-table/department-table.ts diff --git a/uniplanWeb/src/app/core/interfaces/department-elm.ts b/uniplanWeb/src/app/core/interfaces/department-elm.ts new file mode 100644 index 0000000..a81bfe5 --- /dev/null +++ b/uniplanWeb/src/app/core/interfaces/department-elm.ts @@ -0,0 +1,6 @@ +export interface DepartmentElm { + id: string; + departmentName: string; + facultyId: string; + position: number; +} diff --git a/uniplanWeb/src/app/features/department/department-add-form/department-add-form.html b/uniplanWeb/src/app/features/department/department-add-form/department-add-form.html new file mode 100644 index 0000000..902767d --- /dev/null +++ b/uniplanWeb/src/app/features/department/department-add-form/department-add-form.html @@ -0,0 +1,21 @@ + + + + + Име: + + + + + Факултет: + + @for (fac of faculties; track fac) { + + {{ fac.facultyName }} + + } + + + + + diff --git a/uniplanWeb/src/app/features/department/department-add-form/department-add-form.scss b/uniplanWeb/src/app/features/department/department-add-form/department-add-form.scss new file mode 100644 index 0000000..9b05a56 --- /dev/null +++ b/uniplanWeb/src/app/features/department/department-add-form/department-add-form.scss @@ -0,0 +1,14 @@ +mat-form-field { + width: 100%; +} + +mat-dialog-title { + font-size: 20px; + font-weight: bold; + color: #3f51b5; +} + +input { + border-radius: 10px; + width: 100%; +} diff --git a/uniplanWeb/src/app/features/department/department-add-form/department-add-form.spec.ts b/uniplanWeb/src/app/features/department/department-add-form/department-add-form.spec.ts new file mode 100644 index 0000000..77e8e01 --- /dev/null +++ b/uniplanWeb/src/app/features/department/department-add-form/department-add-form.spec.ts @@ -0,0 +1,18 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DepartmentAddForm } from './department-add-form'; + +describe('DepartmentAddForm', () => { + let component: DepartmentAddForm; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [DepartmentAddForm], + }).compileComponents(); + + fixture = TestBed.createComponent(DepartmentAddForm); + component = fixture.componentInstance; + fixture.detectChanges(); + }); +}); diff --git a/uniplanWeb/src/app/features/department/department-add-form/department-add-form.ts b/uniplanWeb/src/app/features/department/department-add-form/department-add-form.ts new file mode 100644 index 0000000..30fa2bd --- /dev/null +++ b/uniplanWeb/src/app/features/department/department-add-form/department-add-form.ts @@ -0,0 +1,71 @@ +import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core'; +import { AddForm } from '../../../core/shared/add-form/add-form'; +import { MatDialogModule, MatDialogRef } from '@angular/material/dialog'; +import { + MatFormField, + MatFormFieldModule, + MatLabel, +} from '@angular/material/form-field'; +import { FormsModule } from '@angular/forms'; +import { MatInputModule } from '@angular/material/input'; +import { MatSelectModule } from '@angular/material/select'; +import { DepartmentService } from '../department-service'; +import { FacultyElm } from '../../../core/interfaces/faculty-elm'; +import { FacultyService } from '../../faculty/faculty-service'; + +@Component({ + selector: 'app-department-add-form', + imports: [ + MatDialogModule, + MatFormField, + MatLabel, + FormsModule, + MatInputModule, + AddForm, + MatFormFieldModule, + MatSelectModule, + ], + templateUrl: './department-add-form.html', + changeDetection: ChangeDetectionStrategy.Eager, + styleUrl: './department-add-form.scss', +}) +export class DepartmentAddForm implements OnInit { + departmentName = ''; + facultyId = ''; + + faculties: FacultyElm[] = []; + + constructor( + private dialogRef: MatDialogRef, + private departmentService: DepartmentService, + private facultyService: FacultyService + ) {} + + ngOnInit(): void { + this.facultyService.getFaculties().subscribe({ + next: (data) => { + this.faculties = data; + }, + error: (err) => console.error('Failed to load faculties', err), + }); + } + + save() { + if (!this.departmentName.trim() || !this.facultyId) { + alert('Please fill all fields.'); + return; + } + + this.departmentService + .createDepartment({ + departmentName: this.departmentName, + facultyId: this.facultyId, + }) + .subscribe({ + next: () => { + this.dialogRef.close(true); + }, + error: () => alert('Failed to create department.'), + }); + } +} diff --git a/uniplanWeb/src/app/features/department/department-delete-form/department-delete-form.html b/uniplanWeb/src/app/features/department/department-delete-form/department-delete-form.html new file mode 100644 index 0000000..d92524f --- /dev/null +++ b/uniplanWeb/src/app/features/department/department-delete-form/department-delete-form.html @@ -0,0 +1,8 @@ + + +

    + Сигурни ли сте, че искате да изтриете катедра с име: + {{ data.departmentName }}? +

    +
    +
    diff --git a/uniplanWeb/src/app/features/department/department-delete-form/department-delete-form.scss b/uniplanWeb/src/app/features/department/department-delete-form/department-delete-form.scss new file mode 100644 index 0000000..e69de29 diff --git a/uniplanWeb/src/app/features/department/department-delete-form/department-delete-form.spec.ts b/uniplanWeb/src/app/features/department/department-delete-form/department-delete-form.spec.ts new file mode 100644 index 0000000..022983d --- /dev/null +++ b/uniplanWeb/src/app/features/department/department-delete-form/department-delete-form.spec.ts @@ -0,0 +1,18 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DepartmentDeleteForm } from './department-delete-form'; + +describe('DepartmentDeleteForm', () => { + let component: DepartmentDeleteForm; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [DepartmentDeleteForm], + }).compileComponents(); + + fixture = TestBed.createComponent(DepartmentDeleteForm); + component = fixture.componentInstance; + fixture.detectChanges(); + }); +}); diff --git a/uniplanWeb/src/app/features/department/department-delete-form/department-delete-form.ts b/uniplanWeb/src/app/features/department/department-delete-form/department-delete-form.ts new file mode 100644 index 0000000..38cc0a7 --- /dev/null +++ b/uniplanWeb/src/app/features/department/department-delete-form/department-delete-form.ts @@ -0,0 +1,37 @@ +import { Component, Inject, ChangeDetectionStrategy } from '@angular/core'; +import { DeleteForm } from '../../../core/shared/delete-form/delete-form'; +import { + MAT_DIALOG_DATA, + MatDialogModule, + MatDialogRef, +} from '@angular/material/dialog'; +import { DepartmentService } from '../department-service'; + +@Component({ + selector: 'app-department-delete-form', + imports: [DeleteForm, MatDialogModule], + templateUrl: './department-delete-form.html', + changeDetection: ChangeDetectionStrategy.Eager, + styleUrl: './department-delete-form.scss', +}) +export class DepartmentDeleteForm { + constructor( + private departmentService: DepartmentService, + private dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) + public data: { id: string; departmentName: string } + ) {} + + deleteDepartment(): void { + this.departmentService + .deleteDepartment(this.data.id) + .subscribe({ + next: () => { + this.dialogRef.close(true); + }, + error: () => { + alert('Възникна грешка при изтриването на катедрата.'); + }, + }); + } +} diff --git a/uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.html b/uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.html new file mode 100644 index 0000000..8882efe --- /dev/null +++ b/uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.html @@ -0,0 +1,19 @@ + + + + Име: + + + + + Факултет: + + @for (fac of faculties; track fac) { + + {{ fac.facultyName }} + + } + + + + diff --git a/uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.scss b/uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.scss new file mode 100644 index 0000000..887baec --- /dev/null +++ b/uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.scss @@ -0,0 +1,13 @@ +mat-form-field { + width: 100%; +} + +mat-dialog-title { + font-size: 20px; + font-weight: bold; + color: #3f51b5; +} + +input { + border-radius: 10px; +} diff --git a/uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.spec.ts b/uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.spec.ts new file mode 100644 index 0000000..38bfd09 --- /dev/null +++ b/uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.spec.ts @@ -0,0 +1,18 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DepartmentEditForm } from './department-edit-form'; + +describe('DepartmentEditForm', () => { + let component: DepartmentEditForm; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [DepartmentEditForm], + }).compileComponents(); + + fixture = TestBed.createComponent(DepartmentEditForm); + component = fixture.componentInstance; + fixture.detectChanges(); + }); +}); diff --git a/uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.ts b/uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.ts new file mode 100644 index 0000000..df91c3d --- /dev/null +++ b/uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.ts @@ -0,0 +1,77 @@ +import { Component, Inject, OnInit, ChangeDetectionStrategy } from '@angular/core'; +import { EditForm } from '../../../core/shared/edit-form/edit-form'; +import { MatFormField, MatLabel } from '@angular/material/form-field'; +import { + MAT_DIALOG_DATA, + MatDialogModule, + MatDialogRef, +} from '@angular/material/dialog'; +import { MatOptionModule } from '@angular/material/core'; +import { MatSelectModule } from '@angular/material/select'; +import { MatInputModule } from '@angular/material/input'; +import { FormsModule } from '@angular/forms'; +import { DepartmentService } from '../department-service'; +import { FacultyService } from '../../faculty/faculty-service'; +import { FacultyElm } from '../../../core/interfaces/faculty-elm'; + +@Component({ + selector: 'app-department-edit-form', + imports: [ + EditForm, + MatDialogModule, + MatFormField, + MatLabel, + FormsModule, + MatInputModule, + MatSelectModule, + MatOptionModule, + ], + templateUrl: './department-edit-form.html', + changeDetection: ChangeDetectionStrategy.Eager, + styleUrl: './department-edit-form.scss', +}) +export class DepartmentEditForm implements OnInit { + departmentName = ''; + facultyId = ''; + + faculties: FacultyElm[] = []; + + constructor( + private dialogRef: MatDialogRef, + private departmentService: DepartmentService, + private facultyService: FacultyService, + @Inject(MAT_DIALOG_DATA) + public data: { + id: string; + departmentName: string; + facultyId?: string; + } + ) { + this.departmentName = data.departmentName; + this.facultyId = data.facultyId || ''; + } + + ngOnInit(): void { + this.facultyService.getFaculties().subscribe({ + next: (data) => (this.faculties = data), + error: (err) => console.error('Failed to load faculties', err), + }); + } + + save() { + if (!this.departmentName.trim()) { + alert('Please enter the department name.'); + return; + } + + this.departmentService + .editDepartment(this.data.id, { + departmentName: this.departmentName, + facultyId: this.facultyId, + }) + .subscribe({ + next: () => this.dialogRef.close(true), + error: () => alert('Failed to update department.'), + }); + } +} diff --git a/uniplanWeb/src/app/features/department/department-filters/department-filters.html b/uniplanWeb/src/app/features/department/department-filters/department-filters.html new file mode 100644 index 0000000..68e3f5a --- /dev/null +++ b/uniplanWeb/src/app/features/department/department-filters/department-filters.html @@ -0,0 +1,14 @@ +
    + + + + +
    diff --git a/uniplanWeb/src/app/features/department/department-filters/department-filters.scss b/uniplanWeb/src/app/features/department/department-filters/department-filters.scss new file mode 100644 index 0000000..bcba9e9 --- /dev/null +++ b/uniplanWeb/src/app/features/department/department-filters/department-filters.scss @@ -0,0 +1,14 @@ +.filters-panel { + display: flex; + flex-wrap: wrap; + gap: 1%; + justify-content: flex-end; + align-items: flex-end; + + height: auto; + padding: 0 10px; +} + +::ng-deep .mat-form-field-infix { + padding: 4px 0 !important; +} diff --git a/uniplanWeb/src/app/features/department/department-filters/department-filters.spec.ts b/uniplanWeb/src/app/features/department/department-filters/department-filters.spec.ts new file mode 100644 index 0000000..144ffeb --- /dev/null +++ b/uniplanWeb/src/app/features/department/department-filters/department-filters.spec.ts @@ -0,0 +1,18 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DepartmentFilters } from './department-filters'; + +describe('DepartmentFilters', () => { + let component: DepartmentFilters; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [DepartmentFilters], + }).compileComponents(); + + fixture = TestBed.createComponent(DepartmentFilters); + component = fixture.componentInstance; + fixture.detectChanges(); + }); +}); diff --git a/uniplanWeb/src/app/features/department/department-filters/department-filters.ts b/uniplanWeb/src/app/features/department/department-filters/department-filters.ts new file mode 100644 index 0000000..e90f1bc --- /dev/null +++ b/uniplanWeb/src/app/features/department/department-filters/department-filters.ts @@ -0,0 +1,30 @@ +import { Component, EventEmitter, Input, Output, ChangeDetectionStrategy } from '@angular/core'; +import { FiltersForm } from '../../../core/shared/filters-form/filters-form'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatInputModule } from '@angular/material/input'; +import { MatIconModule } from '@angular/material/icon'; +import { InputFilter } from '../../../core/shared/input-filter/input-filter'; + +@Component({ + selector: 'app-department-filters', + imports: [ + FiltersForm, + MatFormFieldModule, + MatInputModule, + MatIconModule, + InputFilter, + ], + templateUrl: './department-filters.html', + changeDetection: ChangeDetectionStrategy.Eager, + styleUrl: './department-filters.scss', +}) +export class DepartmentFilters { + @Input() internalSearchText = ''; + + @Input() faculties: { id: string; name: string }[] = []; + + @Input() selectedFaculty = ''; + + @Output() facultyChange = new EventEmitter(); + @Output() searchTextChange = new EventEmitter(); +} diff --git a/uniplanWeb/src/app/features/department/department-options/department-options.html b/uniplanWeb/src/app/features/department/department-options/department-options.html new file mode 100644 index 0000000..1bb70bc --- /dev/null +++ b/uniplanWeb/src/app/features/department/department-options/department-options.html @@ -0,0 +1,2 @@ + diff --git a/uniplanWeb/src/app/features/department/department-options/department-options.scss b/uniplanWeb/src/app/features/department/department-options/department-options.scss new file mode 100644 index 0000000..e170cab --- /dev/null +++ b/uniplanWeb/src/app/features/department/department-options/department-options.scss @@ -0,0 +1,3 @@ +.add-button :hover { + background-color: #e3f2fd; +} diff --git a/uniplanWeb/src/app/features/department/department-options/department-options.spec.ts b/uniplanWeb/src/app/features/department/department-options/department-options.spec.ts new file mode 100644 index 0000000..e13c13a --- /dev/null +++ b/uniplanWeb/src/app/features/department/department-options/department-options.spec.ts @@ -0,0 +1,18 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DepartmentOptions } from './department-options'; + +describe('DepartmentOptions', () => { + let component: DepartmentOptions; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [DepartmentOptions], + }).compileComponents(); + + fixture = TestBed.createComponent(DepartmentOptions); + component = fixture.componentInstance; + fixture.detectChanges(); + }); +}); diff --git a/uniplanWeb/src/app/features/department/department-options/department-options.ts b/uniplanWeb/src/app/features/department/department-options/department-options.ts new file mode 100644 index 0000000..a69a1aa --- /dev/null +++ b/uniplanWeb/src/app/features/department/department-options/department-options.ts @@ -0,0 +1,29 @@ +import { Component, ChangeDetectionStrategy } from '@angular/core'; +import { MatDialog } from '@angular/material/dialog'; +import { DepartmentAddForm } from '../department-add-form/department-add-form'; +import { MatTableModule } from '@angular/material/table'; +import { MatIconModule } from '@angular/material/icon'; +import { MatButtonModule } from '@angular/material/button'; +import { AddButton } from '../../../core/shared/add-button/add-button'; + +@Component({ + selector: 'app-department-options', + imports: [ + MatTableModule, + MatIconModule, + MatButtonModule, + AddButton, + ], + templateUrl: './department-options.html', + changeDetection: ChangeDetectionStrategy.Eager, + styleUrl: './department-options.scss', +}) +export class DepartmentOptions { + constructor(private dialog: MatDialog) {} + + openAddForm() { + this.dialog.open(DepartmentAddForm, { + width: '400px', + }); + } +} diff --git a/uniplanWeb/src/app/features/department/department-service.spec.ts b/uniplanWeb/src/app/features/department/department-service.spec.ts new file mode 100644 index 0000000..6342a3c --- /dev/null +++ b/uniplanWeb/src/app/features/department/department-service.spec.ts @@ -0,0 +1,12 @@ +import { TestBed } from '@angular/core/testing'; + +import { DepartmentService } from './department-service'; + +describe('DepartmentService', () => { + let service: DepartmentService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(DepartmentService); + }); +}); diff --git a/uniplanWeb/src/app/features/department/department-service.ts b/uniplanWeb/src/app/features/department/department-service.ts new file mode 100644 index 0000000..acd16d1 --- /dev/null +++ b/uniplanWeb/src/app/features/department/department-service.ts @@ -0,0 +1,61 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { map, Observable, Subject } from 'rxjs'; +import { DepartmentElm } from '../../core/interfaces/department-elm'; + +@Injectable({ + providedIn: 'root', +}) +export class DepartmentService { + private apiUrl = 'http://localhost:8080/api/departments'; + + refreshNeeded = new Subject(); + + constructor(private http: HttpClient) {} + + getDepartments(): Observable { + return this.http.get(this.apiUrl).pipe( + map((departments) => + departments.map((dept, index) => ({ + id: dept.id, + departmentName: dept.departmentName, + facultyId: dept.facultyId, + position: index + 1, + })) + ) + ); + } + + createDepartment(data: { + departmentName: string; + facultyId: string; + }): Observable { + return this.http.post(`${this.apiUrl}`, data).pipe( + map((res) => { + this.refreshNeeded.next(); + return res; + }) + ); + } + + editDepartment( + id: string, + data: { departmentName: string; facultyId: string } + ): Observable { + return this.http.put(`${this.apiUrl}/${id}`, data).pipe( + map((res) => { + this.refreshNeeded.next(); + return res; + }) + ); + } + + deleteDepartment(id: string): Observable { + return this.http.delete(`${this.apiUrl}/${id}`).pipe( + map((res) => { + this.refreshNeeded.next(); + return res; + }) + ); + } +} diff --git a/uniplanWeb/src/app/features/department/department-table/department-table.html b/uniplanWeb/src/app/features/department/department-table/department-table.html new file mode 100644 index 0000000..4070b1b --- /dev/null +++ b/uniplanWeb/src/app/features/department/department-table/department-table.html @@ -0,0 +1,61 @@ +
    +
    No. + {{ 'features.number-label' | translate }}
    + + + + + + + + + + + + + + + + + +
    No. + Катедра + Факултет + Действия +
    + +
    + + + + + + + + + + + + + + + + + + +
    + {{element.position}} + {{element.departmentName}} + {{ getFacultyName(element.facultyId) }} + + +
    +
    +
    diff --git a/uniplanWeb/src/app/features/department/department-table/department-table.scss b/uniplanWeb/src/app/features/department/department-table/department-table.scss new file mode 100644 index 0000000..6bafc2a --- /dev/null +++ b/uniplanWeb/src/app/features/department/department-table/department-table.scss @@ -0,0 +1,74 @@ +.table-wrapper { + border-radius: 10px; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); + overflow: hidden; + background-color: #fff; +} + +.table-header { + width: 100%; + table-layout: fixed; +} + +.scrollable-body { + max-height: 510px; + overflow-y: auto; + overflow-x: hidden; +} + +@media (min-width: 900px) { + .scrollable-body { + max-height: 510px; + } +} + +@media (min-width: 1800px) { + .scrollable-body { + max-height: 680px; + } +} + +.table-body { + width: 100%; + table-layout: fixed; + border-collapse: collapse; +} + +.table-body tr:hover td { + background-color: #f8f8f8; + cursor: pointer; +} + +th, +td { + text-align: center; + padding: 8px 10px; + word-wrap: break-word; +} + +th { + background-color: #f7f7f7 !important; +} + +td { + background-color: #fff; +} + +.col-position { + width: 80px; + border-right: 1px solid #e0e0e0; +} + +.col-name { + width: 300px; + border-right: 1px solid #e0e0e0; +} + +.col-faculty { + width: 350px; + border-right: 1px solid #e0e0e0; +} + +.col-actions { + width: 150px; +} diff --git a/uniplanWeb/src/app/features/department/department-table/department-table.spec.ts b/uniplanWeb/src/app/features/department/department-table/department-table.spec.ts new file mode 100644 index 0000000..a4b6aa9 --- /dev/null +++ b/uniplanWeb/src/app/features/department/department-table/department-table.spec.ts @@ -0,0 +1,18 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DepartmentTable } from './department-table'; + +describe('DepartmentTable', () => { + let component: DepartmentTable; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [DepartmentTable], + }).compileComponents(); + + fixture = TestBed.createComponent(DepartmentTable); + component = fixture.componentInstance; + fixture.detectChanges(); + }); +}); diff --git a/uniplanWeb/src/app/features/department/department-table/department-table.ts b/uniplanWeb/src/app/features/department/department-table/department-table.ts new file mode 100644 index 0000000..35a4ddd --- /dev/null +++ b/uniplanWeb/src/app/features/department/department-table/department-table.ts @@ -0,0 +1,98 @@ +import { Component, Input, OnInit, ChangeDetectionStrategy } from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { MatIconModule } from '@angular/material/icon'; +import { MatTableModule } from '@angular/material/table'; +import { DepartmentElm } from '../../../core/interfaces/department-elm'; +import { DepartmentEditForm } from '../department-edit-form/department-edit-form'; +import { MatDialog } from '@angular/material/dialog'; +import { DepartmentDeleteForm } from '../department-delete-form/department-delete-form'; +import { DepartmentService } from '../department-service'; +import { FacultyService } from '../../faculty/faculty-service'; + +@Component({ + selector: 'app-department-table', + imports: [MatTableModule, MatIconModule, MatButtonModule], + templateUrl: './department-table.html', + changeDetection: ChangeDetectionStrategy.Eager, + styleUrl: './department-table.scss', +}) +export class DepartmentTable implements OnInit { + displayedColumns: string[] = ['position', 'name', 'faculty', 'actions']; + + dataSource: DepartmentElm[] = []; + facultyMap = new Map(); + + @Input() searchText = ''; + @Input() faculty: string = ''; + + constructor( + private dialog: MatDialog, + private service: DepartmentService, + private facultyService: FacultyService + ) {} + + ngOnInit(): void { + this.loadDepartments(); + this.loadFaculties(); + + this.service.refreshNeeded.subscribe(() => { + this.loadDepartments(); + this.loadFaculties(); + }); + } + + loadDepartments(): void { + this.service.getDepartments().subscribe((data) => { + this.dataSource = data; + }); + } + + loadFaculties(): void { + this.facultyService.getFaculties().subscribe((faculties) => { + this.facultyMap = new Map(faculties.map((f) => [f.id, f.facultyName])); + }); + } + + getFacultyName(id: string): string { + return this.facultyMap.get(id) || '—'; + } + + get filteredDepartments(): DepartmentElm[] { + return this.dataSource.filter((dept) => { + const matchesFaculty = !this.faculty || dept.facultyId === this.faculty; + const matchesSearch = + !this.searchText || + dept.departmentName.toLowerCase().includes(this.searchText.toLowerCase()); + + return matchesFaculty && matchesSearch; + }); + } + + onEdit(element: DepartmentElm): void { + this.dialog.open(DepartmentEditForm, { + data: { + id: element.id, + departmentName: element.departmentName, + facultyId: element.facultyId, + }, + }); + } + + onDelete(element: DepartmentElm): void { + this.dialog.open(DepartmentDeleteForm, { + data: { + id: element.id, + departmentName: element.departmentName, + }, + }); + } + + static getFilterOptions(data: DepartmentElm[], facultyMap: Map) { + const faculties = [...new Set(data.map((e) => e.facultyId))].map((id) => ({ + id, + name: facultyMap.get(id) || '—', + })); + + return { faculties }; + } +} From 02169c90379de221f871c55306d0c6ea201dc0c6 Mon Sep 17 00:00:00 2001 From: Ivcho Date: Tue, 23 Jun 2026 15:51:52 +0300 Subject: [PATCH 09/17] Replaced constructor injection with inject() --- uniplanWeb/src/app/features/department/department-service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/uniplanWeb/src/app/features/department/department-service.ts b/uniplanWeb/src/app/features/department/department-service.ts index acd16d1..d832a94 100644 --- a/uniplanWeb/src/app/features/department/department-service.ts +++ b/uniplanWeb/src/app/features/department/department-service.ts @@ -1,5 +1,5 @@ import { HttpClient } from '@angular/common/http'; -import { Injectable } from '@angular/core'; +import {inject, Injectable} from '@angular/core'; import { map, Observable, Subject } from 'rxjs'; import { DepartmentElm } from '../../core/interfaces/department-elm'; @@ -11,7 +11,7 @@ export class DepartmentService { refreshNeeded = new Subject(); - constructor(private http: HttpClient) {} + private http = inject(HttpClient); getDepartments(): Observable { return this.http.get(this.apiUrl).pipe( From beaa91f5864b0f89716dcb329cce70544f4643b0 Mon Sep 17 00:00:00 2001 From: Ivcho Date: Thu, 25 Jun 2026 13:45:21 +0300 Subject: [PATCH 10/17] Added i18n to department --- uniplanWeb/public/i18n/bg.json | 15 ++++++++++++++- uniplanWeb/public/i18n/en.json | 15 ++++++++++++++- .../department-add-form/department-add-form.html | 6 +++--- .../department-add-form/department-add-form.ts | 2 ++ .../department-delete-form.html | 2 +- .../department-delete-form.ts | 3 ++- .../department-edit-form.html | 6 +++--- .../department-edit-form/department-edit-form.ts | 2 ++ .../department-filters/department-filters.html | 4 ++-- .../department-filters/department-filters.ts | 2 ++ .../department-table/department-table.html | 6 +++--- .../department-table/department-table.ts | 3 ++- 12 files changed, 50 insertions(+), 16 deletions(-) diff --git a/uniplanWeb/public/i18n/bg.json b/uniplanWeb/public/i18n/bg.json index 5d3fe91..d1e3172 100644 --- a/uniplanWeb/public/i18n/bg.json +++ b/uniplanWeb/public/i18n/bg.json @@ -6,6 +6,7 @@ "type-label": "Тип", "study-mode-label": "Форма на обучение", "major-label": "Специалност", + "department-label": "Катедра", "location-label": "Местоположение", "number-label": "No.", "edit": "Редактирай", @@ -40,6 +41,18 @@ "title": "Редактирай специалност" } }, + "department": { + "delete-button": "Изтрий", + "edit-button": "Редактирай", + "uni-label": "Университет", + "delete-confirmation": "Сигурни ли сте, че искате да изтриете катедра с име: ", + "add": { + "title": "Добави катедра" + }, + "edit": { + "title": "Редактирай катедра" + } + }, "student": { "faculty-number-label": "Факултетен номер", "course-label": "Курс", @@ -85,4 +98,4 @@ "logout": "Изход", "login": "Вход" } -} \ No newline at end of file +} diff --git a/uniplanWeb/public/i18n/en.json b/uniplanWeb/public/i18n/en.json index bd15631..fa0afcb 100644 --- a/uniplanWeb/public/i18n/en.json +++ b/uniplanWeb/public/i18n/en.json @@ -6,6 +6,7 @@ "type-label": "Type", "study-mode-label": "Study mode", "major-label": "Major", + "department-label": "Department", "location-label": "Location", "number-label": "No.", "edit": "Edit", @@ -40,6 +41,18 @@ "title": "Edit major" } }, + "department": { + "delete-button": "Delete", + "edit-button": "Edit", + "uni-label": "University", + "delete-confirmation": "Are you sure you want to delete the department named: ", + "add": { + "title": "Add department" + }, + "edit": { + "title": "Edit department" + } + }, "student": { "faculty-number-label": "Student ID", "course-label": "Course", @@ -85,4 +98,4 @@ "logout": "Logout", "login": "Login" } -} \ No newline at end of file +} diff --git a/uniplanWeb/src/app/features/department/department-add-form/department-add-form.html b/uniplanWeb/src/app/features/department/department-add-form/department-add-form.html index 902767d..4b7e0c7 100644 --- a/uniplanWeb/src/app/features/department/department-add-form/department-add-form.html +++ b/uniplanWeb/src/app/features/department/department-add-form/department-add-form.html @@ -1,13 +1,13 @@ - + - Име: + {{ 'features.name-label' | translate }} - Факултет: + {{ 'features.faculty-label' | translate }} @for (fac of faculties; track fac) { diff --git a/uniplanWeb/src/app/features/department/department-add-form/department-add-form.ts b/uniplanWeb/src/app/features/department/department-add-form/department-add-form.ts index 30fa2bd..ae72f6d 100644 --- a/uniplanWeb/src/app/features/department/department-add-form/department-add-form.ts +++ b/uniplanWeb/src/app/features/department/department-add-form/department-add-form.ts @@ -12,6 +12,7 @@ import { MatSelectModule } from '@angular/material/select'; import { DepartmentService } from '../department-service'; import { FacultyElm } from '../../../core/interfaces/faculty-elm'; import { FacultyService } from '../../faculty/faculty-service'; +import {TranslatePipe} from '@ngx-translate/core'; @Component({ selector: 'app-department-add-form', @@ -24,6 +25,7 @@ import { FacultyService } from '../../faculty/faculty-service'; AddForm, MatFormFieldModule, MatSelectModule, + TranslatePipe, ], templateUrl: './department-add-form.html', changeDetection: ChangeDetectionStrategy.Eager, diff --git a/uniplanWeb/src/app/features/department/department-delete-form/department-delete-form.html b/uniplanWeb/src/app/features/department/department-delete-form/department-delete-form.html index d92524f..958f186 100644 --- a/uniplanWeb/src/app/features/department/department-delete-form/department-delete-form.html +++ b/uniplanWeb/src/app/features/department/department-delete-form/department-delete-form.html @@ -1,7 +1,7 @@

    - Сигурни ли сте, че искате да изтриете катедра с име: + {{'department.delete-confirmation' | translate }} {{ data.departmentName }}?

    diff --git a/uniplanWeb/src/app/features/department/department-delete-form/department-delete-form.ts b/uniplanWeb/src/app/features/department/department-delete-form/department-delete-form.ts index 38cc0a7..6f9073b 100644 --- a/uniplanWeb/src/app/features/department/department-delete-form/department-delete-form.ts +++ b/uniplanWeb/src/app/features/department/department-delete-form/department-delete-form.ts @@ -6,10 +6,11 @@ import { MatDialogRef, } from '@angular/material/dialog'; import { DepartmentService } from '../department-service'; +import {TranslatePipe} from '@ngx-translate/core'; @Component({ selector: 'app-department-delete-form', - imports: [DeleteForm, MatDialogModule], + imports: [DeleteForm, MatDialogModule, TranslatePipe], templateUrl: './department-delete-form.html', changeDetection: ChangeDetectionStrategy.Eager, styleUrl: './department-delete-form.scss', diff --git a/uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.html b/uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.html index 8882efe..c0b99cc 100644 --- a/uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.html +++ b/uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.html @@ -1,12 +1,12 @@ - + - Име: + {{ 'features.name-label' | translate }} - Факултет: + {{ 'features.faculty-label' | translate }} @for (fac of faculties; track fac) { diff --git a/uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.ts b/uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.ts index df91c3d..9d534ca 100644 --- a/uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.ts +++ b/uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.ts @@ -13,6 +13,7 @@ import { FormsModule } from '@angular/forms'; import { DepartmentService } from '../department-service'; import { FacultyService } from '../../faculty/faculty-service'; import { FacultyElm } from '../../../core/interfaces/faculty-elm'; +import {TranslatePipe} from '@ngx-translate/core'; @Component({ selector: 'app-department-edit-form', @@ -25,6 +26,7 @@ import { FacultyElm } from '../../../core/interfaces/faculty-elm'; MatInputModule, MatSelectModule, MatOptionModule, + TranslatePipe, ], templateUrl: './department-edit-form.html', changeDetection: ChangeDetectionStrategy.Eager, diff --git a/uniplanWeb/src/app/features/department/department-filters/department-filters.html b/uniplanWeb/src/app/features/department/department-filters/department-filters.html index 68e3f5a..50b2ddd 100644 --- a/uniplanWeb/src/app/features/department/department-filters/department-filters.html +++ b/uniplanWeb/src/app/features/department/department-filters/department-filters.html @@ -1,11 +1,11 @@
    - Катедра + {{ 'features.name-label' | translate }} - Факултет + {{ 'features.faculty-label' | translate }} - Действия + {{ 'features.actions-label' | translate }} diff --git a/uniplanWeb/src/app/features/department/department-table/department-table.ts b/uniplanWeb/src/app/features/department/department-table/department-table.ts index 35a4ddd..e135d5e 100644 --- a/uniplanWeb/src/app/features/department/department-table/department-table.ts +++ b/uniplanWeb/src/app/features/department/department-table/department-table.ts @@ -8,10 +8,11 @@ import { MatDialog } from '@angular/material/dialog'; import { DepartmentDeleteForm } from '../department-delete-form/department-delete-form'; import { DepartmentService } from '../department-service'; import { FacultyService } from '../../faculty/faculty-service'; +import {TranslatePipe} from '@ngx-translate/core'; @Component({ selector: 'app-department-table', - imports: [MatTableModule, MatIconModule, MatButtonModule], + imports: [MatTableModule, MatIconModule, MatButtonModule, TranslatePipe], templateUrl: './department-table.html', changeDetection: ChangeDetectionStrategy.Eager, styleUrl: './department-table.scss', From 8bb3567f93f59574da7f211c398e7b42f2b4dd4e Mon Sep 17 00:00:00 2001 From: Ivcho Date: Thu, 25 Jun 2026 18:00:08 +0300 Subject: [PATCH 11/17] Fixed department compatability with routing --- uniplanWeb/src/app/app.routes.ts | 5 ++ .../navmenu-component/navmenu-component.html | 4 +- .../department-panel/department-panel.html | 16 +++++ .../department-panel/department-panel.scss | 2 + .../department-panel/department-panel.ts | 65 +++++++++++++++++++ 5 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 uniplanWeb/src/app/features/department/department-panel/department-panel.html create mode 100644 uniplanWeb/src/app/features/department/department-panel/department-panel.scss create mode 100644 uniplanWeb/src/app/features/department/department-panel/department-panel.ts diff --git a/uniplanWeb/src/app/app.routes.ts b/uniplanWeb/src/app/app.routes.ts index 69caa0b..4130587 100644 --- a/uniplanWeb/src/app/app.routes.ts +++ b/uniplanWeb/src/app/app.routes.ts @@ -17,6 +17,11 @@ export const routes: Routes = [ loadComponent: () => import('./features/faculty/faculty-panel/faculty-panel').then(m => m.FacultyPanel), }, + { + path: 'department', + loadComponent: () => + import('./features/department/department-panel/department-panel').then(m => m.DepartmentPanel), + }, { path: 'major', loadComponent: () => diff --git a/uniplanWeb/src/app/core/shared/navmenu-component/navmenu-component.html b/uniplanWeb/src/app/core/shared/navmenu-component/navmenu-component.html index 5cf411d..cbc83aa 100644 --- a/uniplanWeb/src/app/core/shared/navmenu-component/navmenu-component.html +++ b/uniplanWeb/src/app/core/shared/navmenu-component/navmenu-component.html @@ -30,7 +30,9 @@

    Logo

  • - {{ 'navmenu.department' | translate }} + + {{ 'navmenu.department' | translate }} +
  • diff --git a/uniplanWeb/src/app/features/department/department-panel/department-panel.html b/uniplanWeb/src/app/features/department/department-panel/department-panel.html new file mode 100644 index 0000000..466caeb --- /dev/null +++ b/uniplanWeb/src/app/features/department/department-panel/department-panel.html @@ -0,0 +1,16 @@ +
    +
    + + + +
    + + +
    diff --git a/uniplanWeb/src/app/features/department/department-panel/department-panel.scss b/uniplanWeb/src/app/features/department/department-panel/department-panel.scss new file mode 100644 index 0000000..2d29f1b --- /dev/null +++ b/uniplanWeb/src/app/features/department/department-panel/department-panel.scss @@ -0,0 +1,2 @@ +@use '../../../../styles/panel' as *; +@include panel-styles(); diff --git a/uniplanWeb/src/app/features/department/department-panel/department-panel.ts b/uniplanWeb/src/app/features/department/department-panel/department-panel.ts new file mode 100644 index 0000000..8237fd1 --- /dev/null +++ b/uniplanWeb/src/app/features/department/department-panel/department-panel.ts @@ -0,0 +1,65 @@ +import { Component, DestroyRef, OnInit, inject } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { catchError, combineLatest, map, merge, of, switchMap } from 'rxjs'; + +import { DepartmentElm } from '../../../core/interfaces/department-elm'; +import { FacultyService } from '../../faculty/faculty-service'; +import { DepartmentOptions } from '../department-options/department-options'; +import { DepartmentFilters } from '../department-filters/department-filters'; +import { DepartmentTable } from '../department-table/department-table'; +import { DepartmentService } from '../department-service'; + +@Component({ + selector: 'app-department-panel', + imports: [DepartmentOptions, DepartmentFilters, DepartmentTable], + templateUrl: './department-panel.html', + styleUrl: './department-panel.scss', +}) +export class DepartmentPanel implements OnInit { + searchText: string = ''; + selectedFaculty: string = ''; + + faculties: { id: string; name: string }[] = []; + + private departments: DepartmentElm[] = []; + private facultyMap: Map = new Map(); + private destroyRef: DestroyRef = inject(DestroyRef); + + constructor( + private departmentService: DepartmentService, + private facultyService: FacultyService, + ) {} + + ngOnInit(): void { + const facultyMap$ = merge(of(undefined), this.facultyService.refreshNeeded).pipe( + switchMap(() => + this.facultyService.getFaculties().pipe( + map((faculties) => new Map(faculties.map((f) => [f.id, f.facultyName]))), + catchError(() => of(this.facultyMap)), + ), + ), + ); + + const departments$ = merge(of(undefined), this.departmentService.refreshNeeded).pipe( + switchMap(() => + this.departmentService.getDepartments().pipe( + catchError(() => of(this.departments)), + ), + ), + ); + + combineLatest([facultyMap$, departments$]) + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe(([facultyMap, departments]) => { + this.facultyMap = facultyMap; + this.departments = departments; + + const options = DepartmentTable.getFilterOptions(this.departments, this.facultyMap); + this.faculties = options.faculties; + + if (this.selectedFaculty && !this.faculties.some((f) => f.id === this.selectedFaculty)) { + this.selectedFaculty = ''; + } + }); + } +} From 9c5b66faaee64e9910953c03ddaa23fa77a03826 Mon Sep 17 00:00:00 2001 From: Ivcho Date: Mon, 29 Jun 2026 11:08:30 +0300 Subject: [PATCH 12/17] Comments resolved, used reactive forms, added access modifiers and types, added translate pipes to hardcoded alerts --- uniplanWeb/public/i18n/bg.json | 11 ++++- uniplanWeb/public/i18n/en.json | 16 +++---- .../department-add-form.html | 12 +++-- .../department-add-form.ts | 42 +++++++++++------ .../department-delete-form.ts | 22 ++++----- .../department-edit-form.html | 36 +++++++++------ .../department-edit-form.ts | 46 +++++++++++++------ .../department-filters.html | 6 +-- .../department-filters/department-filters.ts | 12 ++--- .../department-options/department-options.ts | 2 +- .../features/department/department-service.ts | 12 ++--- .../department-table/department-table.html | 2 +- .../department-table/department-table.ts | 32 +++++++------ 13 files changed, 155 insertions(+), 96 deletions(-) diff --git a/uniplanWeb/public/i18n/bg.json b/uniplanWeb/public/i18n/bg.json index d1e3172..4681fec 100644 --- a/uniplanWeb/public/i18n/bg.json +++ b/uniplanWeb/public/i18n/bg.json @@ -14,7 +14,10 @@ }, "room": { "room-number": "Номер на стая", - "no-rooms": "Няма записани стаи" + "no-rooms": "Няма записани стаи", + "department-label": "Катедра", + "number-label": "№" + }, "faculty": { "delete-button": "Изтрий", @@ -46,6 +49,9 @@ "edit-button": "Редактирай", "uni-label": "Университет", "delete-confirmation": "Сигурни ли сте, че искате да изтриете катедра с име: ", + "create-error": "Възникна грешка при създаването на катедрата.", + "update-error": "Възникна грешка при редактирането на катедрата.", + "delete-error": "Възникна грешка при изтриването на катедрата.", "add": { "title": "Добави катедра" }, @@ -73,7 +79,8 @@ "save-button": "Запази", "delete-button": "Изтрий", "option-all": "Всички", - "confirm-delete": "Потвърдете изтриването" + "confirm-delete": "Потвърдете изтриването", + "required-field": "Това поле е задължително" }, "home": { "title": "Начало!" diff --git a/uniplanWeb/public/i18n/en.json b/uniplanWeb/public/i18n/en.json index fa0afcb..517d38b 100644 --- a/uniplanWeb/public/i18n/en.json +++ b/uniplanWeb/public/i18n/en.json @@ -6,15 +6,9 @@ "type-label": "Type", "study-mode-label": "Study mode", "major-label": "Major", - "department-label": "Department", "location-label": "Location", - "number-label": "No.", - "edit": "Edit", - "delete": "Delete" - }, - "room": { - "room-number": "Room number", - "no-rooms": "No rooms" + "department-label": "Department", + "number-label": "No." }, "faculty": { "delete-button": "Delete", @@ -46,6 +40,9 @@ "edit-button": "Edit", "uni-label": "University", "delete-confirmation": "Are you sure you want to delete the department named: ", + "create-error": "Failed to create department.", + "update-error": "Failed to update department.", + "delete-error": "Failed to delete department.", "add": { "title": "Add department" }, @@ -73,7 +70,8 @@ "save-button": "Save", "delete-button": "Delete", "option-all": "All", - "confirm-delete": "Confirm deletion" + "confirm-delete": "Confirm deletion", + "required-field": "This field is required" }, "home": { "title": "Main!" diff --git a/uniplanWeb/src/app/features/department/department-add-form/department-add-form.html b/uniplanWeb/src/app/features/department/department-add-form/department-add-form.html index 4b7e0c7..f387e91 100644 --- a/uniplanWeb/src/app/features/department/department-add-form/department-add-form.html +++ b/uniplanWeb/src/app/features/department/department-add-form/department-add-form.html @@ -1,20 +1,26 @@ - + {{ 'features.name-label' | translate }} - + + @if (form.controls.departmentName.hasError('required')) { + {{ 'shared.required-field' | translate }} + } {{ 'features.faculty-label' | translate }} - + @for (fac of faculties; track fac) { {{ fac.facultyName }} } + @if (form.controls.facultyId.hasError('required')) { + {{ 'shared.required-field' | translate }} + } diff --git a/uniplanWeb/src/app/features/department/department-add-form/department-add-form.ts b/uniplanWeb/src/app/features/department/department-add-form/department-add-form.ts index ae72f6d..722596d 100644 --- a/uniplanWeb/src/app/features/department/department-add-form/department-add-form.ts +++ b/uniplanWeb/src/app/features/department/department-add-form/department-add-form.ts @@ -6,13 +6,18 @@ import { MatFormFieldModule, MatLabel, } from '@angular/material/form-field'; -import { FormsModule } from '@angular/forms'; +import { + FormControl, + FormGroup, + ReactiveFormsModule, + Validators, +} from '@angular/forms'; import { MatInputModule } from '@angular/material/input'; import { MatSelectModule } from '@angular/material/select'; import { DepartmentService } from '../department-service'; import { FacultyElm } from '../../../core/interfaces/faculty-elm'; import { FacultyService } from '../../faculty/faculty-service'; -import {TranslatePipe} from '@ngx-translate/core'; +import { TranslatePipe, TranslateService } from '@ngx-translate/core'; @Component({ selector: 'app-department-add-form', @@ -20,7 +25,7 @@ import {TranslatePipe} from '@ngx-translate/core'; MatDialogModule, MatFormField, MatLabel, - FormsModule, + ReactiveFormsModule, MatInputModule, AddForm, MatFormFieldModule, @@ -32,15 +37,24 @@ import {TranslatePipe} from '@ngx-translate/core'; styleUrl: './department-add-form.scss', }) export class DepartmentAddForm implements OnInit { - departmentName = ''; - facultyId = ''; + protected readonly form = new FormGroup({ + departmentName: new FormControl('', { + nonNullable: true, + validators: [Validators.required], + }), + facultyId: new FormControl('', { + nonNullable: true, + validators: [Validators.required], + }), + }); - faculties: FacultyElm[] = []; + protected faculties: FacultyElm[] = []; constructor( private dialogRef: MatDialogRef, private departmentService: DepartmentService, - private facultyService: FacultyService + private facultyService: FacultyService, + private translate: TranslateService ) {} ngOnInit(): void { @@ -52,22 +66,24 @@ export class DepartmentAddForm implements OnInit { }); } - save() { - if (!this.departmentName.trim() || !this.facultyId) { - alert('Please fill all fields.'); + protected save(): void { + if (this.form.invalid) { + this.form.markAllAsTouched(); return; } + const { departmentName, facultyId } = this.form.getRawValue(); + this.departmentService .createDepartment({ - departmentName: this.departmentName, - facultyId: this.facultyId, + departmentName: departmentName.trim(), + facultyId, }) .subscribe({ next: () => { this.dialogRef.close(true); }, - error: () => alert('Failed to create department.'), + error: () => alert(this.translate.instant('department.create-error')), }); } } diff --git a/uniplanWeb/src/app/features/department/department-delete-form/department-delete-form.ts b/uniplanWeb/src/app/features/department/department-delete-form/department-delete-form.ts index 6f9073b..2376cd0 100644 --- a/uniplanWeb/src/app/features/department/department-delete-form/department-delete-form.ts +++ b/uniplanWeb/src/app/features/department/department-delete-form/department-delete-form.ts @@ -1,12 +1,8 @@ -import { Component, Inject, ChangeDetectionStrategy } from '@angular/core'; -import { DeleteForm } from '../../../core/shared/delete-form/delete-form'; -import { - MAT_DIALOG_DATA, - MatDialogModule, - MatDialogRef, -} from '@angular/material/dialog'; -import { DepartmentService } from '../department-service'; -import {TranslatePipe} from '@ngx-translate/core'; +import {ChangeDetectionStrategy, Component, Inject} from '@angular/core'; +import {DeleteForm} from '../../../core/shared/delete-form/delete-form'; +import {MAT_DIALOG_DATA, MatDialogModule, MatDialogRef,} from '@angular/material/dialog'; +import {DepartmentService} from '../department-service'; +import {TranslatePipe, TranslateService} from '@ngx-translate/core'; @Component({ selector: 'app-department-delete-form', @@ -19,11 +15,13 @@ export class DepartmentDeleteForm { constructor( private departmentService: DepartmentService, private dialogRef: MatDialogRef, + private translate: TranslateService, @Inject(MAT_DIALOG_DATA) public data: { id: string; departmentName: string } - ) {} + ) { + } - deleteDepartment(): void { + protected deleteDepartment(): void { this.departmentService .deleteDepartment(this.data.id) .subscribe({ @@ -31,7 +29,7 @@ export class DepartmentDeleteForm { this.dialogRef.close(true); }, error: () => { - alert('Възникна грешка при изтриването на катедрата.'); + alert(this.translate.instant('department.delete-error')); }, }); } diff --git a/uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.html b/uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.html index c0b99cc..01aa3c1 100644 --- a/uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.html +++ b/uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.html @@ -1,19 +1,29 @@ - - {{ 'features.name-label' | translate }} - - +
    - - {{ 'features.faculty-label' | translate }} - - @for (fac of faculties; track fac) { - - {{ fac.facultyName }} - + + {{ 'features.name-label' | translate }} + + @if (form.controls.departmentName.hasError('required')) { + {{ 'shared.required-field' | translate }} } - - + + + + {{ 'features.faculty-label' | translate }} + + @for (fac of faculties; track fac) { + + {{ fac.facultyName }} + + } + + @if (form.controls.facultyId.hasError('required')) { + {{ 'shared.required-field' | translate }} + } + + +
    diff --git a/uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.ts b/uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.ts index 9d534ca..536a123 100644 --- a/uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.ts +++ b/uniplanWeb/src/app/features/department/department-edit-form/department-edit-form.ts @@ -9,11 +9,16 @@ import { import { MatOptionModule } from '@angular/material/core'; import { MatSelectModule } from '@angular/material/select'; import { MatInputModule } from '@angular/material/input'; -import { FormsModule } from '@angular/forms'; +import { + FormControl, + FormGroup, + ReactiveFormsModule, + Validators, +} from '@angular/forms'; import { DepartmentService } from '../department-service'; import { FacultyService } from '../../faculty/faculty-service'; import { FacultyElm } from '../../../core/interfaces/faculty-elm'; -import {TranslatePipe} from '@ngx-translate/core'; +import { TranslatePipe, TranslateService } from '@ngx-translate/core'; @Component({ selector: 'app-department-edit-form', @@ -22,7 +27,7 @@ import {TranslatePipe} from '@ngx-translate/core'; MatDialogModule, MatFormField, MatLabel, - FormsModule, + ReactiveFormsModule, MatInputModule, MatSelectModule, MatOptionModule, @@ -33,15 +38,18 @@ import {TranslatePipe} from '@ngx-translate/core'; styleUrl: './department-edit-form.scss', }) export class DepartmentEditForm implements OnInit { - departmentName = ''; - facultyId = ''; + protected readonly form: FormGroup<{ + departmentName: FormControl; + facultyId: FormControl; + }>; - faculties: FacultyElm[] = []; + protected faculties: FacultyElm[] = []; constructor( private dialogRef: MatDialogRef, private departmentService: DepartmentService, private facultyService: FacultyService, + private translate: TranslateService, @Inject(MAT_DIALOG_DATA) public data: { id: string; @@ -49,8 +57,16 @@ export class DepartmentEditForm implements OnInit { facultyId?: string; } ) { - this.departmentName = data.departmentName; - this.facultyId = data.facultyId || ''; + this.form = new FormGroup({ + departmentName: new FormControl(data.departmentName ?? '', { + nonNullable: true, + validators: [Validators.required], + }), + facultyId: new FormControl(data.facultyId ?? '', { + nonNullable: true, + validators: [Validators.required], + }), + }); } ngOnInit(): void { @@ -60,20 +76,22 @@ export class DepartmentEditForm implements OnInit { }); } - save() { - if (!this.departmentName.trim()) { - alert('Please enter the department name.'); + protected save(): void { + if (this.form.invalid) { + this.form.markAllAsTouched(); return; } + const { departmentName, facultyId } = this.form.getRawValue(); + this.departmentService .editDepartment(this.data.id, { - departmentName: this.departmentName, - facultyId: this.facultyId, + departmentName: departmentName.trim(), + facultyId, }) .subscribe({ next: () => this.dialogRef.close(true), - error: () => alert('Failed to update department.'), + error: () => alert(this.translate.instant('department.update-error')), }); } } diff --git a/uniplanWeb/src/app/features/department/department-filters/department-filters.html b/uniplanWeb/src/app/features/department/department-filters/department-filters.html index 50b2ddd..d59383e 100644 --- a/uniplanWeb/src/app/features/department/department-filters/department-filters.html +++ b/uniplanWeb/src/app/features/department/department-filters/department-filters.html @@ -1,13 +1,13 @@
    diff --git a/uniplanWeb/src/app/features/department/department-filters/department-filters.ts b/uniplanWeb/src/app/features/department/department-filters/department-filters.ts index aea5e91..c66ae38 100644 --- a/uniplanWeb/src/app/features/department/department-filters/department-filters.ts +++ b/uniplanWeb/src/app/features/department/department-filters/department-filters.ts @@ -1,4 +1,4 @@ -import { Component, EventEmitter, Input, Output, ChangeDetectionStrategy } from '@angular/core'; +import { Component, ChangeDetectionStrategy, input, output } from '@angular/core'; import { FiltersForm } from '../../../core/shared/filters-form/filters-form'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; @@ -21,12 +21,12 @@ import {TranslatePipe} from '@ngx-translate/core'; styleUrl: './department-filters.scss', }) export class DepartmentFilters { - @Input() internalSearchText = ''; + readonly internalSearchText = input(''); - @Input() faculties: { id: string; name: string }[] = []; + readonly faculties = input<{ id: string; name: string }[]>([]); - @Input() selectedFaculty = ''; + readonly selectedFaculty = input(''); - @Output() facultyChange = new EventEmitter(); - @Output() searchTextChange = new EventEmitter(); + readonly facultyChange = output(); + readonly searchTextChange = output(); } diff --git a/uniplanWeb/src/app/features/department/department-options/department-options.ts b/uniplanWeb/src/app/features/department/department-options/department-options.ts index a69a1aa..ad086f1 100644 --- a/uniplanWeb/src/app/features/department/department-options/department-options.ts +++ b/uniplanWeb/src/app/features/department/department-options/department-options.ts @@ -21,7 +21,7 @@ import { AddButton } from '../../../core/shared/add-button/add-button'; export class DepartmentOptions { constructor(private dialog: MatDialog) {} - openAddForm() { + protected openAddForm(): void { this.dialog.open(DepartmentAddForm, { width: '400px', }); diff --git a/uniplanWeb/src/app/features/department/department-service.ts b/uniplanWeb/src/app/features/department/department-service.ts index d832a94..e27f536 100644 --- a/uniplanWeb/src/app/features/department/department-service.ts +++ b/uniplanWeb/src/app/features/department/department-service.ts @@ -29,8 +29,8 @@ export class DepartmentService { createDepartment(data: { departmentName: string; facultyId: string; - }): Observable { - return this.http.post(`${this.apiUrl}`, data).pipe( + }): Observable { + return this.http.post(`${this.apiUrl}`, data).pipe( map((res) => { this.refreshNeeded.next(); return res; @@ -41,8 +41,8 @@ export class DepartmentService { editDepartment( id: string, data: { departmentName: string; facultyId: string } - ): Observable { - return this.http.put(`${this.apiUrl}/${id}`, data).pipe( + ): Observable { + return this.http.put(`${this.apiUrl}/${id}`, data).pipe( map((res) => { this.refreshNeeded.next(); return res; @@ -50,8 +50,8 @@ export class DepartmentService { ); } - deleteDepartment(id: string): Observable { - return this.http.delete(`${this.apiUrl}/${id}`).pipe( + deleteDepartment(id: string): Observable { + return this.http.delete(`${this.apiUrl}/${id}`).pipe( map((res) => { this.refreshNeeded.next(); return res; diff --git a/uniplanWeb/src/app/features/department/department-table/department-table.html b/uniplanWeb/src/app/features/department/department-table/department-table.html index 23923ec..5a13a35 100644 --- a/uniplanWeb/src/app/features/department/department-table/department-table.html +++ b/uniplanWeb/src/app/features/department/department-table/department-table.html @@ -2,7 +2,7 @@ - diff --git a/uniplanWeb/src/app/features/department/department-table/department-table.ts b/uniplanWeb/src/app/features/department/department-table/department-table.ts index e135d5e..fad38e0 100644 --- a/uniplanWeb/src/app/features/department/department-table/department-table.ts +++ b/uniplanWeb/src/app/features/department/department-table/department-table.ts @@ -1,4 +1,4 @@ -import { Component, Input, OnInit, ChangeDetectionStrategy } from '@angular/core'; +import { Component, OnInit, ChangeDetectionStrategy, input } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; import { MatTableModule } from '@angular/material/table'; @@ -23,8 +23,8 @@ export class DepartmentTable implements OnInit { dataSource: DepartmentElm[] = []; facultyMap = new Map(); - @Input() searchText = ''; - @Input() faculty: string = ''; + readonly searchText = input(''); + readonly faculty = input(''); constructor( private dialog: MatDialog, @@ -42,34 +42,37 @@ export class DepartmentTable implements OnInit { }); } - loadDepartments(): void { + private loadDepartments(): void { this.service.getDepartments().subscribe((data) => { this.dataSource = data; }); } - loadFaculties(): void { + private loadFaculties(): void { this.facultyService.getFaculties().subscribe((faculties) => { this.facultyMap = new Map(faculties.map((f) => [f.id, f.facultyName])); }); } - getFacultyName(id: string): string { + protected getFacultyName(id: string): string { return this.facultyMap.get(id) || '—'; } - get filteredDepartments(): DepartmentElm[] { + protected get filteredDepartments(): DepartmentElm[] { + const faculty = this.faculty(); + const searchText = this.searchText(); + return this.dataSource.filter((dept) => { - const matchesFaculty = !this.faculty || dept.facultyId === this.faculty; + const matchesFaculty = !faculty || dept.facultyId === faculty; const matchesSearch = - !this.searchText || - dept.departmentName.toLowerCase().includes(this.searchText.toLowerCase()); + !searchText || + dept.departmentName.toLowerCase().includes(searchText.toLowerCase()); return matchesFaculty && matchesSearch; }); } - onEdit(element: DepartmentElm): void { + protected onEdit(element: DepartmentElm): void { this.dialog.open(DepartmentEditForm, { data: { id: element.id, @@ -79,7 +82,7 @@ export class DepartmentTable implements OnInit { }); } - onDelete(element: DepartmentElm): void { + protected onDelete(element: DepartmentElm): void { this.dialog.open(DepartmentDeleteForm, { data: { id: element.id, @@ -88,7 +91,10 @@ export class DepartmentTable implements OnInit { }); } - static getFilterOptions(data: DepartmentElm[], facultyMap: Map) { + static getFilterOptions( + data: DepartmentElm[], + facultyMap: Map, + ): { faculties: { id: string; name: string }[] } { const faculties = [...new Set(data.map((e) => e.facultyId))].map((id) => ({ id, name: facultyMap.get(id) || '—', From 25a6c748030937735c9bab5b95bb9c5be06ece14 Mon Sep 17 00:00:00 2001 From: Ivcho Date: Tue, 30 Jun 2026 15:06:25 +0300 Subject: [PATCH 13/17] Fixed compatability with extracted URLs --- uniplanWeb/src/app/config/endpoints.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/uniplanWeb/src/app/config/endpoints.ts b/uniplanWeb/src/app/config/endpoints.ts index 53a9589..d11935e 100644 --- a/uniplanWeb/src/app/config/endpoints.ts +++ b/uniplanWeb/src/app/config/endpoints.ts @@ -5,4 +5,5 @@ export const API_ENDPOINTS = { courses: `${environment.baseUrl}/courses`, faculties: `${environment.baseUrl}/faculties`, majors: `${environment.baseUrl}/majors`, + departments: `${environment.baseUrl}/departments`, }; From 574e031ef4c79ab5c6aef0d1da37ba3a56aaa7e6 Mon Sep 17 00:00:00 2001 From: Ivcho Date: Tue, 30 Jun 2026 15:24:50 +0300 Subject: [PATCH 14/17] Refactored services to use the extracted URLs properly --- .../src/app/features/department/department-service.ts | 11 +++++------ .../src/app/features/faculty/faculty-service.ts | 2 +- uniplanWeb/src/app/features/major/major-service.ts | 3 +-- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/uniplanWeb/src/app/features/department/department-service.ts b/uniplanWeb/src/app/features/department/department-service.ts index e27f536..982d138 100644 --- a/uniplanWeb/src/app/features/department/department-service.ts +++ b/uniplanWeb/src/app/features/department/department-service.ts @@ -2,19 +2,18 @@ import { HttpClient } from '@angular/common/http'; import {inject, Injectable} from '@angular/core'; import { map, Observable, Subject } from 'rxjs'; import { DepartmentElm } from '../../core/interfaces/department-elm'; +import { API_ENDPOINTS } from '../../config/endpoints'; @Injectable({ providedIn: 'root', }) export class DepartmentService { - private apiUrl = 'http://localhost:8080/api/departments'; - refreshNeeded = new Subject(); private http = inject(HttpClient); getDepartments(): Observable { - return this.http.get(this.apiUrl).pipe( + return this.http.get(API_ENDPOINTS.departments).pipe( map((departments) => departments.map((dept, index) => ({ id: dept.id, @@ -30,7 +29,7 @@ export class DepartmentService { departmentName: string; facultyId: string; }): Observable { - return this.http.post(`${this.apiUrl}`, data).pipe( + return this.http.post(`${API_ENDPOINTS.departments}`, data).pipe( map((res) => { this.refreshNeeded.next(); return res; @@ -42,7 +41,7 @@ export class DepartmentService { id: string, data: { departmentName: string; facultyId: string } ): Observable { - return this.http.put(`${this.apiUrl}/${id}`, data).pipe( + return this.http.put(`${API_ENDPOINTS.departments}/${id}`, data).pipe( map((res) => { this.refreshNeeded.next(); return res; @@ -51,7 +50,7 @@ export class DepartmentService { } deleteDepartment(id: string): Observable { - return this.http.delete(`${this.apiUrl}/${id}`).pipe( + return this.http.delete(`${API_ENDPOINTS.departments}/${id}`).pipe( map((res) => { this.refreshNeeded.next(); return res; diff --git a/uniplanWeb/src/app/features/faculty/faculty-service.ts b/uniplanWeb/src/app/features/faculty/faculty-service.ts index 1c423f0..65fdb1a 100644 --- a/uniplanWeb/src/app/features/faculty/faculty-service.ts +++ b/uniplanWeb/src/app/features/faculty/faculty-service.ts @@ -2,7 +2,7 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { map, Observable, Subject } from 'rxjs'; import { FacultyElm } from '../../core/interfaces/faculty-elm'; -import {API_ENDPOINTS} from '../../config/endpoints'; +import { API_ENDPOINTS } from '../../config/endpoints'; @Injectable({ providedIn: 'root', diff --git a/uniplanWeb/src/app/features/major/major-service.ts b/uniplanWeb/src/app/features/major/major-service.ts index c7a4626..f65d6c4 100644 --- a/uniplanWeb/src/app/features/major/major-service.ts +++ b/uniplanWeb/src/app/features/major/major-service.ts @@ -2,8 +2,7 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { map, Observable, Subject, switchMap } from 'rxjs'; import { MajorElm } from '../../core/interfaces/major-elm'; -import {environment} from '../../../environments/environment'; -import {API_ENDPOINTS} from '../../config/endpoints'; +import { API_ENDPOINTS } from '../../config/endpoints'; @Injectable({ providedIn: 'root', From 1eb2e45a194075cca420d7ea6841ba0d44b204b6 Mon Sep 17 00:00:00 2001 From: Ivcho Date: Thu, 2 Jul 2026 13:39:27 +0300 Subject: [PATCH 15/17] Department made compatible with changes on routing --- .../department-table/department-table.spec.ts | 10 ++++++++-- .../department-table/department-table.ts | 14 +++++++------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/uniplanWeb/src/app/features/department/department-table/department-table.spec.ts b/uniplanWeb/src/app/features/department/department-table/department-table.spec.ts index a4b6aa9..46b62bf 100644 --- a/uniplanWeb/src/app/features/department/department-table/department-table.spec.ts +++ b/uniplanWeb/src/app/features/department/department-table/department-table.spec.ts @@ -1,5 +1,5 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; - +import { translateTestingProviders } from '@testing/translate-testing'; import { DepartmentTable } from './department-table'; describe('DepartmentTable', () => { @@ -9,10 +9,16 @@ describe('DepartmentTable', () => { beforeEach(async () => { await TestBed.configureTestingModule({ imports: [DepartmentTable], - }).compileComponents(); + providers: [...translateTestingProviders] + }) + .compileComponents(); fixture = TestBed.createComponent(DepartmentTable); component = fixture.componentInstance; fixture.detectChanges(); }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); }); diff --git a/uniplanWeb/src/app/features/department/department-table/department-table.ts b/uniplanWeb/src/app/features/department/department-table/department-table.ts index fad38e0..a2bd96e 100644 --- a/uniplanWeb/src/app/features/department/department-table/department-table.ts +++ b/uniplanWeb/src/app/features/department/department-table/department-table.ts @@ -1,4 +1,4 @@ -import { Component, OnInit, ChangeDetectionStrategy, input } from '@angular/core'; +import { Component, OnInit, ChangeDetectionStrategy, input, signal } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; import { MatTableModule } from '@angular/material/table'; @@ -20,8 +20,8 @@ import {TranslatePipe} from '@ngx-translate/core'; export class DepartmentTable implements OnInit { displayedColumns: string[] = ['position', 'name', 'faculty', 'actions']; - dataSource: DepartmentElm[] = []; - facultyMap = new Map(); + dataSource = signal([]); + facultyMap = signal(new Map()); readonly searchText = input(''); readonly faculty = input(''); @@ -44,25 +44,25 @@ export class DepartmentTable implements OnInit { private loadDepartments(): void { this.service.getDepartments().subscribe((data) => { - this.dataSource = data; + this.dataSource.set(data); }); } private loadFaculties(): void { this.facultyService.getFaculties().subscribe((faculties) => { - this.facultyMap = new Map(faculties.map((f) => [f.id, f.facultyName])); + this.facultyMap.set(new Map(faculties.map((f) => [f.id, f.facultyName]))); }); } protected getFacultyName(id: string): string { - return this.facultyMap.get(id) || '—'; + return this.facultyMap().get(id) || '—'; } protected get filteredDepartments(): DepartmentElm[] { const faculty = this.faculty(); const searchText = this.searchText(); - return this.dataSource.filter((dept) => { + return this.dataSource().filter((dept) => { const matchesFaculty = !faculty || dept.facultyId === faculty; const matchesSearch = !searchText || From 4f66c46ce299d997ed7a2a0cc2fdc1ccbfe5db7e Mon Sep 17 00:00:00 2001 From: Ivcho Date: Thu, 2 Jul 2026 13:42:50 +0300 Subject: [PATCH 16/17] DFixed small issue with input import --- .../features/department/department-table/department-table.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uniplanWeb/src/app/features/department/department-table/department-table.ts b/uniplanWeb/src/app/features/department/department-table/department-table.ts index 1a91fc8..e6643b6 100644 --- a/uniplanWeb/src/app/features/department/department-table/department-table.ts +++ b/uniplanWeb/src/app/features/department/department-table/department-table.ts @@ -8,7 +8,7 @@ import { DepartmentDeleteForm } from '../department-delete-form/department-delet import { DepartmentService } from '../department-service'; import { FacultyService } from '../../faculty/faculty-service'; import {TranslatePipe} from '@ngx-translate/core'; -import {ChangeDetectionStrategy, Component, OnInit} from '@angular/core'; +import {ChangeDetectionStrategy, Component, input, OnInit} from '@angular/core'; @Component({ selector: 'app-department-table', From 1269ef678ba128c0bfae882ead5b6417e47af5a8 Mon Sep 17 00:00:00 2001 From: Ivcho Date: Tue, 7 Jul 2026 13:48:24 +0300 Subject: [PATCH 17/17] used signals --- .../department-table/department-table.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/uniplanWeb/src/app/features/department/department-table/department-table.ts b/uniplanWeb/src/app/features/department/department-table/department-table.ts index e6643b6..69b43c6 100644 --- a/uniplanWeb/src/app/features/department/department-table/department-table.ts +++ b/uniplanWeb/src/app/features/department/department-table/department-table.ts @@ -8,7 +8,7 @@ import { DepartmentDeleteForm } from '../department-delete-form/department-delet import { DepartmentService } from '../department-service'; import { FacultyService } from '../../faculty/faculty-service'; import {TranslatePipe} from '@ngx-translate/core'; -import {ChangeDetectionStrategy, Component, input, OnInit} from '@angular/core'; +import {ChangeDetectionStrategy, Component, input, OnInit, signal} from '@angular/core'; @Component({ selector: 'app-department-table', @@ -20,8 +20,8 @@ import {ChangeDetectionStrategy, Component, input, OnInit} from '@angular/core'; export class DepartmentTable implements OnInit { displayedColumns: string[] = ['position', 'name', 'faculty', 'actions']; - dataSource: DepartmentElm[] = []; - facultyMap = new Map(); + dataSource = signal([]); + facultyMap = signal(new Map()); readonly searchText = input(''); readonly faculty = input(''); @@ -44,25 +44,25 @@ export class DepartmentTable implements OnInit { private loadDepartments(): void { this.service.getDepartments().subscribe((data) => { - this.dataSource = data; + this.dataSource.set(data); }); } private loadFaculties(): void { this.facultyService.getFaculties().subscribe((faculties) => { - this.facultyMap = new Map(faculties.map((f) => [f.id, f.facultyName])); + this.facultyMap.set(new Map(faculties.map((f) => [f.id, f.facultyName]))); }); } protected getFacultyName(id: string): string { - return this.facultyMap.get(id) || '—'; + return this.facultyMap().get(id) || '—'; } protected get filteredDepartments(): DepartmentElm[] { const faculty = this.faculty(); const searchText = this.searchText(); - return this.dataSource.filter((dept) => { + return this.dataSource().filter((dept) => { const matchesFaculty = !faculty || dept.facultyId === faculty; const matchesSearch = !searchText ||
    No. + {{ 'features.number-label' | translate }}