Refactor users/roles

oggui/calendar
Manuel Aranda Rosales 2024-10-03 13:02:36 +02:00
parent eaad62fb2e
commit 9226dd50d6
39 changed files with 643 additions and 492 deletions

View File

@ -1,2 +1,2 @@
#NG_APP_BASE_API_URL=http://127.0.0.1:8090
NG_APP_BASE_API_URL=http://127.0.0.1:8001
NG_APP_BASE_API_URL=http://127.0.0.1:8080

View File

@ -28,7 +28,6 @@ import { MatTableModule } from '@angular/material/table';
import { MatDialogModule } from '@angular/material/dialog';
import { AddUserModalComponent } from './components/admin/users/users/add-user-modal/add-user-modal.component';
import { MatSelectModule } from '@angular/material/select';
import { EditUserModalComponent } from './components/admin/users/users/edit-user-modal/edit-user-modal.component';
import { AddRoleModalComponent } from './components/admin/roles/roles/add-role-modal/add-role-modal.component';
import { ChangePasswordModalComponent } from './components/admin/users/users/change-password-modal/change-password-modal.component';
import { GroupsComponent } from './components/groups/groups.component';
@ -114,7 +113,6 @@ import { OrganizationalUnitTabViewComponent } from './components/groups/componen
UsersComponent,
RolesComponent,
AddUserModalComponent,
EditUserModalComponent,
AddRoleModalComponent,
ChangePasswordModalComponent,
GroupsComponent,

View File

@ -1,10 +1,30 @@
.role-form .form-field {
display: block;
margin-bottom: 10px; /* Puedes ajustar el valor para cambiar la separación */
}
.checkbox-group label {
display: block;
margin-bottom: 8px; /* Ajusta este valor según necesites */
}
.full-width {
width: 100%;
}
.form-container {
padding: 40px;
}
.form-group {
margin-top: 20px;
margin-bottom: 26px;
}
.full-width {
width: 100%;
margin-bottom: 16px;
}
.checkbox-group {
margin: 15px 0;
align-items: flex-start;
}
.time-fields {
display: flex;
gap: 15px; /* Espacio entre los campos */
}
.time-field {
flex: 1;
}

View File

@ -1,7 +1,7 @@
<h1 mat-dialog-title i18n="@@dialogTitleAddRole">Añadir Rol (TBD)</h1>
<div mat-dialog-content>
<h1 mat-dialog-title i18n="@@dialogTitleAddRole">Añadir Rol</h1>
<mat-dialog-content class="form-container">
<form [formGroup]="roleForm" class="role-form">
<mat-form-field class="form-field">
<mat-form-field appearance="fill" class="full-width">
<mat-label i18n="@@labelRoleName">Nombre</mat-label>
<input matInput formControlName="name" required>
</mat-form-field>
@ -15,8 +15,8 @@
<p><mat-checkbox formControlName="userRole" i18n="@@checkboxUserRole">Usuario</mat-checkbox></p>
</section>
</form>
</div>
<div mat-dialog-actions>
</mat-dialog-content>
<mat-dialog-actions align="end">
<button mat-button (click)="onNoClick()" i18n="@@buttonCancel">Cancelar</button>
<button mat-button (click)="onSubmit()" i18n="@@buttonAdd">Añadir</button>
</div>
</mat-dialog-actions>

View File

@ -1,7 +1,9 @@
import { HttpClient } from '@angular/common/http';
import { Component, Inject } from '@angular/core';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { FormBuilder, FormGroup } from '@angular/forms';
import {FormBuilder, FormGroup, Validators} from '@angular/forms';
import {DataService} from "../data.service";
import {ToastrService} from "ngx-toastr";
@Component({
selector: 'app-add-role-modal',
@ -10,21 +12,49 @@ import { FormBuilder, FormGroup } from '@angular/forms';
})
export class AddRoleModalComponent {
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
roleForm: FormGroup;
roleForm: FormGroup<any>;
roleId: string | null = null;
constructor(
public dialogRef: MatDialogRef<AddRoleModalComponent>,
@Inject(MAT_DIALOG_DATA) public data: any,
private http: HttpClient,
private fb: FormBuilder
private fb: FormBuilder,
private dataService: DataService,
private toastService: ToastrService
) {
this.roleForm = this.fb.group({
name: [''],
name: ['', Validators.required],
superAdmin: [false],
orgAdmin: [false],
orgOperator: [false],
orgMinimal: [false],
userRole: [false]
userRole: [false],
});
}
ngOnInit(): void {
if (this.data) {
this.load()
}
}
load(): void {
this.dataService.getUserGroup(this.data).subscribe({
next: (response) => {
this.roleForm.patchValue({
name: response.name,
superAdmin: response.permissions.includes('ROLE_SUPER_ADMIN'),
orgAdmin: response.permissions.includes('ROLE_ORGANIZATIONAL_UNIT_ADMIN'),
orgOperator: response.permissions.includes('ROLE_ORGANIZATIONAL_UNIT_OPERATOR'),
orgMinimal: response.permissions.includes('ROLE_ORGANIZATIONAL_UNIT_MINIMAL'),
userRole: response.permissions.includes('ROLE_USER'),
});
this.roleId = response['@id'];
},
error: (err) => {
console.error('Error fetching remote calendar:', err);
}
});
}
@ -43,21 +73,36 @@ export class AddRoleModalComponent {
if (formValues.orgMinimal) selectedPermissions.push('ROLE_ORGANIZATIONAL_UNIT_MINIMAL');
if (formValues.userRole) selectedPermissions.push('ROLE_USER');
const payload = {
name: formValues.name,
permissions: selectedPermissions,
enabled: true
enabled: true
};
this.http.post(`${this.baseUrl}/user-groups`, payload).subscribe(
(response) => {
this.dialogRef.close();
},
(error) => {
console.error('Error al añadir rol', error);
}
);
if (this.roleId) {
this.http.put(`${this.baseUrl}${this.roleId}`, payload).subscribe(
(response) => {
this.toastService.success('Rol actualizado correctamente');
this.dialogRef.close();
},
(error) => {
this.toastService.error('Error al editar el rol:', error);
console.error('Error al editar el rol', error);
}
);
} else {
this.http.post(`${this.baseUrl}/user-groups`, payload).subscribe(
(response) => {
this.toastService.success('Rol añadido correctamente');
this.dialogRef.close();
},
(error) => {
this.toastService.error('Error al añadir rol:', error);
console.error('Error al añadir rol', error);
}
);
}
}
}
}

View File

@ -0,0 +1,51 @@
import { Injectable } from '@angular/core';
import {HttpClient, HttpParams} from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class DataService {
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
private apiUrl = `${this.baseUrl}/user-groups?page=1&itemsPerPage=1000`;
constructor(private http: HttpClient) {}
getUserGroups(filters: { [key: string]: string }): Observable<{ totalItems: any; data: any }> {
const params = new HttpParams({ fromObject: filters });
return this.http.get<any>(this.apiUrl, { params }).pipe(
map(response => {
if (response['hydra:member'] && Array.isArray(response['hydra:member'])) {
return {
data: response['hydra:member'],
totalItems: response['hydra:totalItems']
}
} else {
throw new Error('Unexpected response format');
}
}),
catchError(error => {
console.error('Error fetching user groups', error);
return throwError(error);
})
);
}
getUserGroup(id: string): Observable<any> {
return this.http.get<any>(`${this.baseUrl}${id}`).pipe(
map(response => {
if (response.name && response.permissions) {
return response;
} else {
throw new Error('Unexpected response format');
}
}),
catchError(error => {
console.error('Error fetching user group', error);
return throwError(error);
})
);
}
}

View File

@ -3,19 +3,43 @@ table {
margin-top: 50px;
}
.header-container {
margin-top: 16px;
.search-container {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
width: 100%;
padding: 0 5px;
box-sizing: border-box;
}
.header-container h1 {
margin: 0;
.divider {
margin: 20px 0;
}
.search-string {
flex: 2;
padding: 5px;
}
.search-boolean {
flex: 1;
padding: 5px;
}
.header-container {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
}
.mat-elevation-z8 {
box-shadow: 0px 0px 0px rgba(0,0,0,0.2);
}
.paginator-container {
display: flex;
justify-content: end;
margin-bottom: 30px;
}

View File

@ -1,20 +1,39 @@
<div class="header-container">
<h1 i18n="@@headerRoleManagement">Gestión de roles</h1>
<button mat-flat-button color="primary" (click)="addUser()" i18n="@@buttonAddRole">+ Añadir</button>
<h2 class="title" i18n="@@adminImagesTitle">Administrar Roles</h2>
<div class="images-button-row">
<button mat-flat-button color="primary" (click)="addUser()">Añadir rol</button>
</div>
</div>
<mat-divider class="divider"></mat-divider>
<div class="search-container">
<mat-form-field appearance="fill" class="search-string">
<mat-label i18n="@@searchLabel">Buscar nombre de rol</mat-label>
<input matInput placeholder="Búsqueda" [(ngModel)]="filters['name']" (keyup.enter)="search()" i18n-placeholder="@@searchPlaceholder">
<mat-icon matSuffix>search</mat-icon>
<mat-hint i18n="@@searchHint">Pulsar 'enter' para buscar</mat-hint>
</mat-form-field>
</div>
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
<td mat-cell *matCellDef="let role"> {{ column.cell(role) }} </td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef i18n="@@headerActions">Acciones</th>
<td mat-cell *matCellDef="let role">
<button mat-button color="warn" [disabled]="role.name === 'Super Admin'" (click)="deleteRole(role)" i18n="@@buttonDelete">Eliminar</button>
<th mat-header-cell *matHeaderCellDef i18n="@@columnActions" style="text-align: center;">Acciones</th>
<td mat-cell *matCellDef="let role" style="text-align: center;">
<button mat-icon-button color="primary" (click)="editRole(role)" i18n="@@editImage"> <mat-icon>edit</mat-icon></button>
<button mat-icon-button color="warn" (click)="deleteRole(role)" i18n="@@buttonDelete" [disabled]="role.permissions.includes('ROLE_SUPER_ADMIN')"><mat-icon>delete</mat-icon></button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
<div class="paginator-container">
<mat-paginator [length]="length"
[pageSize]="itemsPerPage"
[pageIndex]="page"
[pageSizeOptions]="pageSizeOptions"
(page)="onPageChange($event)">
</mat-paginator>
</div>

View File

@ -1,11 +1,13 @@
import { Component, OnInit } from '@angular/core';
import { RoleService } from './roles.service';
import { MatTableDataSource } from '@angular/material/table';
import { MatDialog } from '@angular/material/dialog';
import { AddRoleModalComponent } from './add-role-modal/add-role-modal.component';
import { DeleteModalComponent } from '../../../../shared/delete_modal/delete-modal/delete-modal.component';
import { HttpClient } from '@angular/common/http';
import { ToastrService } from 'ngx-toastr';
import {DataService} from "./data.service";
import {CreateCalendarComponent} from "../../../calendar/create-calendar/create-calendar.component";
import {PageEvent} from "@angular/material/paginator";
@Component({
selector: 'app-roles',
@ -15,6 +17,13 @@ import { ToastrService } from 'ngx-toastr';
export class RolesComponent implements OnInit {
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
dataSource = new MatTableDataSource<any>();
filters: { [key: string]: string } = {};
loading:boolean = false;
length: number = 0;
itemsPerPage: number = 10;
page: number = 0;
pageSizeOptions: number[] = [5, 10, 20, 40, 100];
columns = [
{
columnDef: 'id',
@ -34,49 +43,81 @@ export class RolesComponent implements OnInit {
];
displayedColumns = [...this.columns.map(column => column.columnDef), 'actions'];
constructor(private roleService: RoleService, public dialog: MatDialog, private http: HttpClient,
private toastService: ToastrService) {}
private apiUrl = `${this.baseUrl}/user-groups`;
constructor(
public dialog: MatDialog,
private http: HttpClient,
private dataService: DataService,
private toastService: ToastrService
) {}
ngOnInit() {
this.loadRoles();
this.search();
}
loadRoles() {
this.roleService.getRoles().subscribe(response => {
console.log(response);
this.dataSource.data = response['hydra:member'];
});
search() {
this.http.get<any>(`${this.apiUrl}?&page=${this.page + 1}&itemsPerPage=${this.itemsPerPage}`, { params: this.filters }).subscribe(
(data) => {
this.dataSource.data = data['hydra:member'];
this.length = data['hydra:totalItems'];
},
(error) => {
console.error('Error fetching commands', error);
}
);
}
addUser() {
const dialogRef = this.dialog.open(AddRoleModalComponent);
const dialogRef = this.dialog.open(AddRoleModalComponent, {
width: '600px'
});
dialogRef.afterClosed().subscribe((result) => {
this.loadRoles();
this.search();
});
}
editRole(role: any): void {
const dialogRef = this.dialog.open(AddRoleModalComponent, {
width: '600px',
data: role['@id']
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.search();
}
});
}
deleteRole(role: any): void {
const dialogRef = this.dialog.open(DeleteModalComponent, {
width: '300px',
data: { name: role.name }
width: '500px',
data: { name: role.name }
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
const apiUrl = `${this.baseUrl}/user-groups/${role.uuid}`;
this.http.delete(apiUrl).subscribe({
next: () => {
this.loadRoles();
this.toastService.success('Role deleted successfully');
this.search();
this.toastService.success('Role deleted successfully');
},
error: (error) => {
this.toastService.error('Error deleting role:', error);
this.toastService.error('Error deleting role:', error);
}
});
}
});
}
onPageChange(event: any): void {
this.page = event.pageIndex;
this.itemsPerPage = event.pageSize;
this.length = event.length;
this.search();
}
}

View File

@ -1,24 +0,0 @@
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs';
interface Role {
'@id': string;
name: string;
permissions: string[];
}
@Injectable({
providedIn: 'root'
})
export class RoleService {
private apiUrl = import.meta.env.NG_APP_BASE_API_URL;
constructor(private http: HttpClient) {}
getRoles(): Observable<{ 'hydra:member': Role[] }> {
return this.http.get<{ 'hydra:member': Role[] }>(`${this.apiUrl}/user-groups?page=1&itemsPerPage=30`);
}
}

View File

@ -1,9 +1,30 @@
.user-form .form-field {
display: block;
margin-bottom: 10px; /* Puedes ajustar el valor para cambiar la separación */
.full-width {
width: 100%;
}
.form-container {
padding: 40px;
}
.checkbox-group label {
display: block;
margin-bottom: 8px; /* Ajusta este valor según necesites */
.form-group {
margin-top: 20px;
margin-bottom: 26px;
}
.full-width {
width: 100%;
margin-bottom: 16px;
}
.checkbox-group {
margin: 15px 0;
align-items: flex-start;
}
.time-fields {
display: flex;
gap: 15px; /* Espacio entre los campos */
}
.time-field {
flex: 1;
}

View File

@ -1,35 +1,35 @@
<h1 mat-dialog-title i18n="@@dialogTitleAddUser">Añadir Usuario</h1>
<div mat-dialog-content>
<h1 mat-dialog-title i18n="@@dialogTitleAddRole">Añadir Usuario</h1>
<mat-dialog-content class="form-container">
<form [formGroup]="userForm" class="user-form">
<mat-form-field class="form-field">
<mat-form-field appearance="fill" class="full-width">
<mat-label i18n="@@addUserlabelUsername">Nombre de usuario</mat-label>
<input matInput formControlName="username" required>
</mat-form-field>
<mat-form-field class="form-field">
<mat-form-field appearance="fill" class="full-width">
<mat-label i18n="@@addUserlabelPassword">Contraseña</mat-label>
<input matInput formControlName="password" type="password" required>
</mat-form-field>
<mat-form-field class="form-field">
<mat-form-field appearance="fill" class="full-width">
<mat-label i18n="@@labelRole">Rol</mat-label>
<mat-select formControlName="role">
<mat-select formControlName="role" required>
<mat-option *ngFor="let group of userGroups" [value]="group['@id']">
{{ group.name }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field class="form-field">
<mat-form-field appearance="fill" class="full-width">
<mat-label i18n="@@labelOrganizationalUnit">Unidad organiativa</mat-label>
<mat-select multiple formControlName="organizationalUnit">
<mat-select multiple formControlName="organizationalUnits">
<mat-option *ngFor="let unit of organizationalUnits" [value]="unit['@id']">
{{unit.name}}
</mat-option>
</mat-select>
</mat-form-field>
</form>
</div>
<div mat-dialog-actions>
</mat-dialog-content>
<mat-dialog-actions align="end">
<button mat-button (click)="onNoClick()" i18n="@@buttonCancel">Cancelar</button>
<button mat-button [disabled]="!userForm.valid" (click)="onSubmit()" i18n="@@buttonAdd">Añadir</button>
</div>
<button mat-button (click)="onSubmit()" i18n="@@buttonAdd">Añadir</button>
</mat-dialog-actions>

View File

@ -1,8 +1,9 @@
import { Component, EventEmitter, Inject, OnInit, Output } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { UserService } from '../users.service';
import {ToastrService} from "ngx-toastr";
import {HttpClient} from "@angular/common/http";
import {DataService} from "../data.service";
interface UserGroup {
'@id': string;
@ -16,34 +17,61 @@ interface UserGroup {
styleUrls: ['./add-user-modal.component.css']
})
export class AddUserModalComponent implements OnInit {
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
@Output() userAdded = new EventEmitter<void>();
userForm: FormGroup;
userForm: FormGroup<any>;
userGroups: UserGroup[] = [];
organizationalUnits: any[] = [];
userId: string | null = null;
constructor(
public dialogRef: MatDialogRef<AddUserModalComponent>,
@Inject(MAT_DIALOG_DATA) public data: any,
private http: HttpClient,
private fb: FormBuilder,
private userService: UserService,
private dataService: DataService,
private toastService: ToastrService
) {
this.userForm = this.fb.group({
username: ['', Validators.required],
password: ['', Validators.required],
role: ['', Validators.required],
organizationalUnit: [[], Validators.required]
organizationalUnits: [[]]
});
}
ngOnInit(): void {
this.userService.getUserGroups().subscribe((data) => {
this.dataService.getUserGroups().subscribe((data) => {
this.userGroups = data['hydra:member'];
});
this.userService.getOrganizationalUnits().subscribe((data) => {
this.dataService.getOrganizationalUnits().subscribe((data) => {
this.organizationalUnits = data['hydra:member'].filter((item: any) => item.type === 'organizational-unit');
});
if (this.data) {
this.load()
}
}
load(): void {
this.dataService.getUser(this.data).subscribe({
next: (response) => {
console.log(response);
const organizationalUnitIds = response.allowedOrganizationalUnits.map((unit: any) => unit['@id']);
// Patch the values to the form
this.userForm.patchValue({
username: response.username,
role: response.userGroups[0]['@id'],
organizationalUnits: organizationalUnitIds
});
this.userId = response['@id'];
},
error: (err) => {
console.error('Error fetching remote calendar:', err);
}
});
}
onNoClick(): void {
@ -52,7 +80,7 @@ export class AddUserModalComponent implements OnInit {
onSubmit(): void {
if (this.userForm.valid) {
const userPayload = {
const payload = {
username: this.userForm.value.username,
allowedOrganizationalUnits: this.userForm.value.organizationalUnit,
password: this.userForm.value.password,
@ -60,27 +88,29 @@ export class AddUserModalComponent implements OnInit {
userGroups: [this.userForm.value.role ]
};
this.userService.addUser(userPayload).subscribe(
response => {
console.log('User playload:', userPayload);
console.log('User added successfully:', response);
this.userAdded.emit();
this.dialogRef.close(this.userForm.value);
this.openSnackBar(false, 'Usuario creado correctamente')
},
error => {
console.error('Error adding user:', error);
this.openSnackBar(true, error.error['hydra:description']);
// Agregar alguna lógica para manejar el error en la interfaz de usuario
}
);
if (this.userId) {
this.http.put(`${this.baseUrl}${this.userId}`, payload).subscribe(
(response) => {
this.toastService.success('Usuario editado correctamente');
this.dialogRef.close();
},
(error) => {
this.toastService.error(error['error']['hydra:description']);
console.error('Error al editar el rol', error);
}
);
} else {
this.http.post(`${this.baseUrl}/users`, payload).subscribe(
(response) => {
this.toastService.success('Usuario añadido correctamente');
this.dialogRef.close();
},
(error) => {
this.toastService.error(error['error']['hydra:description']);
console.error('Error al añadir añadido', error);
}
);
}
}
}
openSnackBar(isError: boolean, message: string) {
if (isError) {
this.toastService.error(' Error al eliminar la entidad: ' + message, 'Error');
} else
this.toastService.success(message, 'Éxito');
}
}

View File

@ -1,5 +1,5 @@
<h1 mat-dialog-title i18n="@@dialogTitleEditUser">Editar Usuario</h1>
<div mat-dialog-content>
<mat-dialog-content class="form-container">
<form [formGroup]="userForm" class="user-form">
<mat-form-field class="form-field">
<mat-label i18n="@@labelCurrentPassword">Contraseña actual</mat-label>
@ -14,8 +14,6 @@
<input matInput formControlName="repeatNewPassword" type="password">
</mat-form-field>
<mat-spinner *ngIf="loading"></mat-spinner>
<div class="error-message" *ngIf="passwordMismatch" i18n="@@errorPasswordMismatch">
Las contraseñas no coinciden
</div>
@ -24,8 +22,8 @@
{{ resetPasswordError }}
</div>
</form>
</div>
<div mat-dialog-actions>
</mat-dialog-content>
<mat-dialog-actions align="end">
<button mat-button (click)="onNoClick()" i18n="@@buttonCancel">Cancelar</button>
<button mat-button (click)="onSubmit()" [disabled]="loading" i18n="@@buttonEdit">Editar</button>
</div>
</mat-dialog-actions>

View File

@ -1,9 +1,9 @@
import { Component, EventEmitter, Inject, Output } from '@angular/core';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { EditUserModalComponent } from '../edit-user-modal/edit-user-modal.component';
import { UserService } from '../users.service';
import { DataService } from '../data.service';
import {AddUserModalComponent} from "../add-user-modal/add-user-modal.component";
import {ToastrService} from "ngx-toastr";
@Component({
selector: 'app-change-password-modal',
templateUrl: './change-password-modal.component.html',
@ -18,10 +18,11 @@ export class ChangePasswordModalComponent {
resetPasswordError: string = '';
constructor(
public dialogRef: MatDialogRef<EditUserModalComponent>,
public dialogRef: MatDialogRef<AddUserModalComponent>,
@Inject(MAT_DIALOG_DATA) public data: any,
private fb: FormBuilder,
private userService: UserService
private dataService: DataService,
private toastService: ToastrService
) {
this.userForm = this.fb.group({
currentPassword: ['', Validators.required],
@ -49,20 +50,21 @@ export class ChangePasswordModalComponent {
newPassword: this.userForm.value.newPassword,
repeatNewPassword: this.userForm.value.repeatNewPassword
};
console.log("THIS IS THE USER PAYLOAD: ", userPayload);
this.userService.changePassword(this.data.uuid, userPayload).subscribe(
response => {
console.log('User updated successfully:', response);
this.dataService.changePassword(this.data.uuid, userPayload).subscribe({
next: () => {
this.userEdited.emit();
this.dialogRef.close(this.userForm.value);
this.dialogRef.close();
this.toastService.success('Contraseña cambiada con éxito');
},
error => {
console.error('Error updating user:', error.error['hydra:description']);
this.resetPasswordError = error.error['hydra:description']
error: (err) => {
console.error('Error changing password:', err);
this.toastService.error(err.error['hydra:description']);
this.resetPasswordError = err.error.message;
this.updateError = true;
this.loading = false
this.loading = false;
}
);
});
} else {
console.error('Form is invalid');
this.passwordMismatch = this.userForm.hasError('mismatch');

View File

@ -0,0 +1,88 @@
import { Injectable } from '@angular/core';
import {HttpClient, HttpParams} from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class DataService {
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
private apiUrl = `${this.baseUrl}/users?page=1&itemsPerPage=1000`;
constructor(private http: HttpClient) {}
getUsers(filters: { [key: string]: string }): Observable<{ totalItems: any; data: any }> {
const params = new HttpParams({ fromObject: filters });
return this.http.get<any>(this.apiUrl, { params }).pipe(
map(response => {
if (response['hydra:member'] && Array.isArray(response['hydra:member'])) {
return {
data: response['hydra:member'],
totalItems: response['hydra:totalItems']
}
} else {
throw new Error('Unexpected response format');
}
}),
catchError(error => {
console.error('Error fetching users', error);
return throwError(error);
})
);
}
getUser(id: string): Observable<any> {
return this.http.get<any>(`${this.baseUrl}${id}`).pipe(
map(response => {
if (response.username) {
return response;
} else {
throw new Error('Unexpected response format');
}
}),
catchError(error => {
console.error('Error fetching user group', error);
return throwError(error);
})
);
}
getUserGroups(): Observable<any> {
return this.http.get<any>(`${this.baseUrl}/user-groups`).pipe(
map(response => {
if (response['hydra:member'] && Array.isArray(response['hydra:member'])) {
return response;
} else {
throw new Error('Unexpected response format');
}
}),
catchError(error => {
console.error('Error fetching user groups', error);
return throwError(error);
})
);
}
getOrganizationalUnits(): Observable<any> {
return this.http.get<any>(`${this.baseUrl}/organizational-units`).pipe(
map(response => {
if (response['hydra:member'] && Array.isArray(response['hydra:member'])) {
return response;
} else {
throw new Error('Unexpected response format');
}
}),
catchError(error => {
console.error('Error fetching organizational units', error);
return throwError(error);
})
);
}
changePassword(userId: number, userPayload: any): Observable<any> {
return this.http.put(`${this.baseUrl}/users/${userId}/reset-password`, userPayload);
}
}

View File

@ -1,16 +0,0 @@
.user-form .form-field {
display: block;
margin-bottom: 10px; /* Puedes ajustar el valor para cambiar la separación */
}
.checkbox-group label {
display: block;
margin-bottom: 8px; /* Ajusta este valor según necesites */
}
.loading-container{
display: flex;
justify-content: center;
align-items: center;
height: 100%;
}

View File

@ -1,35 +0,0 @@
<h1 mat-dialog-title i18n="@@dialogTitleEditUser">Editar Usuario</h1>
<mat-spinner *ngIf="loading" class="loading-container"></mat-spinner>
<div *ngIf="!loading" mat-dialog-content>
<form [formGroup]="userForm" class="user-form">
<mat-form-field class="form-field">
<mat-label i18n="@@editUserlabelUsername">Nombre de usuario</mat-label>
<input matInput formControlName="username">
</mat-form-field>
<mat-form-field class="form-field">
<mat-label i18n="@@editUserlabelPassword">Contraseña</mat-label>
<input matInput formControlName="password" type="password">
</mat-form-field>
<mat-form-field class="form-field">
<mat-label i18n="@@labelRole">Rol</mat-label>
<mat-select multiple formControlName="userGroups">
<mat-option *ngFor="let group of userGroups" [value]="group['@id']">
{{ group.name }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field class="form-field">
<mat-label i18n="@@labelOrgUnit">Unidad organizativa</mat-label>
<mat-select multiple formControlName="allowedOrganizationalUnits">
<mat-option *ngFor="let unit of organizationalUnits" [value]="unit['@id']">
{{unit.name}}
</mat-option>
</mat-select>
</mat-form-field>
</form>
</div>
<div mat-dialog-actions>
<button mat-button (click)="onNoClick()" i18n="@@buttonCancel">Cancelar</button>
<button mat-button (click)="onSubmit()" i18n="@@buttonEdit">Editar</button>
</div>

View File

@ -1,40 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { ReactiveFormsModule } from '@angular/forms';
import { EditUserModalComponent } from './edit-user-modal.component';
import { UserService } from '../users.service';
import { ToastrService } from 'ngx-toastr';
import { of } from 'rxjs';
describe('EditUserModalComponent', () => {
let component: EditUserModalComponent;
let fixture: ComponentFixture<EditUserModalComponent>;
let dialogRefSpy: jasmine.SpyObj<MatDialogRef<EditUserModalComponent>>;
let userServiceSpy: jasmine.SpyObj<UserService>;
let toastServiceSpy: jasmine.SpyObj<ToastrService>;
beforeEach(async () => {
dialogRefSpy = jasmine.createSpyObj('MatDialogRef', ['close']);
userServiceSpy = jasmine.createSpyObj('UserService', ['getUserGroups', 'getOrganizationalUnits']);
toastServiceSpy = jasmine.createSpyObj('ToastrService', ['success', 'error']);
await TestBed.configureTestingModule({
declarations: [EditUserModalComponent],
imports: [ReactiveFormsModule],
providers: [
{ provide: MatDialogRef, useValue: dialogRefSpy },
{ provide: MAT_DIALOG_DATA, useValue: { user: { username: 'testUser', userGroups: [], allowedOrganizationalUnits: [] } } },
{ provide: UserService, useValue: userServiceSpy },
{ provide: ToastrService, useValue: toastServiceSpy }
]
}).compileComponents();
fixture = TestBed.createComponent(EditUserModalComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -1,82 +0,0 @@
import { Component, EventEmitter, Inject, OnInit, Output } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { UserService } from '../users.service';
import {ToastrService} from "ngx-toastr";
@Component({
selector: 'app-edit-user-modal',
templateUrl: './edit-user-modal.component.html',
styleUrls: ['./edit-user-modal.component.css']
})
export class EditUserModalComponent implements OnInit {@Output() userEdited = new EventEmitter<void>();
userForm: FormGroup;
userGroups: any[] = [];
organizationalUnits: any[] = [];
loading:boolean = false;
constructor(
public dialogRef: MatDialogRef<EditUserModalComponent>,
@Inject(MAT_DIALOG_DATA) public data: any,
private fb: FormBuilder,
private userService: UserService, // Inyecta el servicio
private toastService: ToastrService
) {
this.userForm = this.fb.group({
username: [this.data.user.username],
password: [null],
userGroups: [this.data.user.userGroups.map((group: { '@id': any; }) => group['@id'])],
allowedOrganizationalUnits: [this.data.user.allowedOrganizationalUnits.map((unit: { '@id': any; }) => unit['@id'])]
});
}
ngOnInit(): void {
this.loading = true
this.userService.getUserGroups().subscribe((data) => {
this.userGroups = data['hydra:member'];
});
this.userService.getOrganizationalUnits().subscribe((data) => {
this.organizationalUnits = data['hydra:member'].filter((item: any) => item.type === 'organizational-unit');
this.loading = false
}, (error) => {
console.error('Error fetching organizational units', error);
this.loading = false
});
}
onNoClick(): void {
this.dialogRef.close();
}
onSubmit(): void {
console.log(this.userForm.value);
const userPayload = {
username: this.userForm.value.username,
allowedOrganizationalUnits: this.userForm.value.allowedOrganizationalUnits,
password: this.userForm.value.password,
enabled: true,
userGroups: this.userForm.value.userGroups
};
this.userService.updateUser(this.data.user.uuid, userPayload).subscribe(
response => {
console.log('User added successfully:', response);
this.userEdited.emit();
this.dialogRef.close(this.userForm.value);
this.openSnackBar(false, 'Usuario actualizado correctamente')
},
error => {
console.error('Error adding user:', error);
this.openSnackBar(true, error.message);
// Agregar alguna lógica para manejar el error en la interfaz de usuario
}
);
}
openSnackBar(isError: boolean, message: string) {
if (isError) {
this.toastService.error(' Error al eliminar la entidad: ' + message, 'Error');
} else
this.toastService.success(message, 'Éxito');
}
}

View File

@ -3,18 +3,43 @@ table {
margin-top: 50px;
}
.header-container {
margin-top: 16px;
.search-container {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
width: 100%;
padding: 0 5px;
box-sizing: border-box;
}
.header-container h1 {
margin: 0;
.divider {
margin: 20px 0;
}
.search-string {
flex: 2;
padding: 5px;
}
.search-boolean {
flex: 1;
padding: 5px;
}
.header-container {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
}
.mat-elevation-z8 {
box-shadow: 0px 0px 0px rgba(0,0,0,0.2);
}
.paginator-container {
display: flex;
justify-content: end;
margin-bottom: 30px;
}

View File

@ -1,6 +1,17 @@
<div class="header-container">
<h1 i18n="@@headerUserManagement">Gestión de usuarios</h1>
<button mat-flat-button color="primary" (click)="addUser()" i18n="@@buttonAddUser">+ Añadir</button>
<h2 class="title" i18n="@@adminImagesTitle">Administrar usuarios</h2>
<div class="images-button-row">
<button mat-flat-button color="primary" (click)="addUser()">Añadir usuarios</button>
</div>
</div>
<mat-divider class="divider"></mat-divider>
<div class="search-container">
<mat-form-field appearance="fill" class="search-string">
<mat-label i18n="@@searchLabel">Buscar nombre de usuario</mat-label>
<input matInput placeholder="Búsqueda" [(ngModel)]="filters['name']" (keyup.enter)="search()" i18n-placeholder="@@searchPlaceholder">
<mat-icon matSuffix>search</mat-icon>
<mat-hint i18n="@@searchHint">Pulsar 'enter' para buscar</mat-hint>
</mat-form-field>
</div>
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
@ -10,13 +21,20 @@
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef i18n="@@columnActions">Acciones</th>
<td mat-cell *matCellDef="let user">
<button mat-button color="primary" (click)="editUser(user)" i18n="@@buttonEditUser">Editar</button>
<button mat-button color="warn" (click)="deleteUser(user)" i18n="@@buttonDeleteUser">Eliminar</button>
<th mat-header-cell *matHeaderCellDef i18n="@@columnActions" style="text-align: center;">Acciones</th>
<td mat-cell *matCellDef="let user" style="text-align: center;">
<button mat-icon-button color="primary" (click)="editUser(user)" i18n="@@editImage"> <mat-icon>edit</mat-icon></button>
<button mat-icon-button color="warn" (click)="deleteUser(user)" i18n="@@buttonDelete"><mat-icon>delete</mat-icon></button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
<div class="paginator-container">
<mat-paginator [length]="length"
[pageSize]="itemsPerPage"
[pageIndex]="page"
[pageSizeOptions]="pageSizeOptions"
(page)="onPageChange($event)">
</mat-paginator>
</div>

View File

@ -5,7 +5,6 @@ import { MatDialogModule } from '@angular/material/dialog';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { ToastrService } from 'ngx-toastr';
import { of } from 'rxjs';
import { UserService } from './users.service';
import { NO_ERRORS_SCHEMA } from '@angular/core'; // Add this import for schema
// Create a mock for UserService
@ -33,13 +32,13 @@ describe('UsersComponent', () => {
HttpClientTestingModule,
],
providers: [
{ provide: UserService, useClass: MockUserService },
{ useClass: MockUserService },
{ provide: ToastrService, useValue: { success: () => {} } }, // Mock ToastrService
],
schemas: [NO_ERRORS_SCHEMA], // Use this to ignore unrecognized components in template
})
.compileComponents();
fixture = TestBed.createComponent(UsersComponent);
component = fixture.componentInstance;
fixture.detectChanges();

View File

@ -1,12 +1,12 @@
import { Component, OnInit } from '@angular/core';
import { UserService } from './users.service';
import { MatTableDataSource } from '@angular/material/table';
import { MatDialog } from '@angular/material/dialog';
import { AddUserModalComponent } from './add-user-modal/add-user-modal.component';
import { EditUserModalComponent } from './edit-user-modal/edit-user-modal.component';
import { DeleteModalComponent } from '../../../../shared/delete_modal/delete-modal/delete-modal.component';
import { HttpClient } from '@angular/common/http';
import { ToastrService } from 'ngx-toastr';
import {DataService} from "./data.service";
import {AddRoleModalComponent} from "../../roles/roles/add-role-modal/add-role-modal.component";
@Component({
selector: 'app-users',
@ -17,6 +17,12 @@ import { ToastrService } from 'ngx-toastr';
export class UsersComponent implements OnInit {
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
dataSource = new MatTableDataSource<any>();
filters: { [key: string]: string } = {};
loading:boolean = false;
length: number = 0;
itemsPerPage: number = 10;
page: number = 0;
pageSizeOptions: number[] = [5, 10, 20, 40, 100];
columns = [
{
columnDef: 'id',
@ -41,55 +47,68 @@ export class UsersComponent implements OnInit {
];
displayedColumns = [...this.columns.map(column => column.columnDef), 'actions'];
constructor(private userService: UserService, public dialog: MatDialog,
private apiUrl = `${this.baseUrl}/users`;
constructor(
public dialog: MatDialog,
private http: HttpClient,
private toastService: ToastrService) {}
private dataService: DataService,
private toastService: ToastrService
) {}
ngOnInit() {
this.loadUsers();
this.search();
}
loadUsers() {
this.userService.getUsers().subscribe(response => {
this.dataSource.data = response['hydra:member'];
});
search() {
this.http.get<any>(`${this.apiUrl}?&page=${this.page + 1}&itemsPerPage=${this.itemsPerPage}`, { params: this.filters }).subscribe(
(data) => {
this.dataSource.data = data['hydra:member'];
this.length = data['hydra:totalItems'];
},
(error) => {
console.error('Error fetching commands', error);
}
);
}
addUser() {
const dialogRef = this.dialog.open(AddUserModalComponent, { width: '500px' });
dialogRef.componentInstance.userAdded.subscribe(() => {
this.loadUsers();
this.search();
});
}
editUser(user: any) {
// Implementar la lógica de edición
const dialogRef = this.dialog.open(EditUserModalComponent, {
data: { user: user },
width: '500px'
editUser(user: any): void {
const dialogRef = this.dialog.open(AddUserModalComponent, {
width: '600px',
data: user['@id']
});
dialogRef.componentInstance.userEdited.subscribe(() => {
this.loadUsers();
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.search();
}
});
}
deleteUser(user: any): void {
const dialogRef = this.dialog.open(DeleteModalComponent, {
width: '300px',
data: { name: user.name }
width: '500px',
data: { name: user.username }
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
const apiUrl = `${this.baseUrl}/users/${user.uuid}`;
this.http.delete(apiUrl).subscribe({
next: () => {
console.log('User deleted successfully');
this.toastService.success('User deleted successfully');
this.loadUsers();
this.search();
},
error: (error) => {
console.error('Error deleting user:', error);
@ -100,5 +119,12 @@ export class UsersComponent implements OnInit {
}
});
}
onPageChange(event: any): void {
this.page = event.pageIndex;
this.itemsPerPage = event.pageSize;
this.length = event.length;
this.search();
}
}

View File

@ -1,66 +0,0 @@
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs';
interface UserPayload {
username: string;
password: string;
enabled: boolean;
userGroups: string[];
allowedOrganizationalUnits: any[];
}
interface ChangePasswordPayload {
currentPassword: string;
newPassword: string;
repeatNewPassword: string;
}
interface UserGroup {
'@id': string;
name: string;
role: string[];
}
@Injectable({
providedIn: 'root'
})
export class UserService {
private apiUrl = import.meta.env.NG_APP_BASE_API_URL;
constructor(private http: HttpClient) {}
addUser(userPayload: UserPayload): Observable<any> {
const headers = new HttpHeaders({
'Content-Type': 'application/ld+json',
});
return this.http.post(`${this.apiUrl}/users`, userPayload, { headers });
}
updateUser(userId: number, userPayload: UserPayload): Observable<any> {
const headers = new HttpHeaders({
'Content-Type': 'application/ld+json',
});
return this.http.put(`${this.apiUrl}/users/${userId}`, userPayload, { headers });
}
changePassword(userId: number, userPayload: ChangePasswordPayload): Observable<any> {
const headers = new HttpHeaders({
'Content-Type': 'application/ld+json',
});
return this.http.put(`${this.apiUrl}/users/${userId}/reset-password`, userPayload, { headers });
}
getUserGroups(): Observable<{ 'hydra:member': UserGroup[] }> {
return this.http.get<{ 'hydra:member': UserGroup[] }>(`${this.apiUrl}/user-groups`);
}
getUsers(): Observable<any> {
return this.http.get<any>(`${this.apiUrl}/users?page=1&itemsPerPage=30`);
}
getOrganizationalUnits(): Observable<any> {
return this.http.get<any>(`${this.apiUrl}/organizational-units?page=1&itemsPerPage=10000`);
}
}

View File

@ -81,22 +81,3 @@ table {
justify-content: end;
margin-bottom: 30px;
}
.example-headers-align .mat-expansion-panel-header-description {
justify-content: space-between;
align-items: center;
}
.example-headers-align .mat-mdc-form-field + .mat-mdc-form-field {
margin-left: 8px;
}
.example-button-row {
display: table-cell;
max-width: 600px;
}
.example-button-row .mat-mdc-button-base {
margin: 8px 8px 8px 0;
}

View File

@ -23,7 +23,6 @@ export class CalendarComponent implements OnInit {
itemsPerPage: number = 10;
page: number = 1;
pageSizeOptions: number[] = [5, 10, 20, 40, 100];
selectedElements: string[] = [];
loading:boolean = false;
filters: { [key: string]: string } = {};
alertMessage: string | null = null;

View File

@ -37,13 +37,13 @@ export class CreateCalendarRuleComponent {
this.calendarId = this.data.calendar
if (this.data) {
this.isEditMode = true;
this.availableFromDate = this.data.rule.availableFromDate;
this.availableToDate = this.data.rule.availableToDate;
this.isRemoteAvailable = this.data.rule.isRemoteAvailable;
this.availableReason = this.data.rule.availableReason;
this.busyFromHour = this.data.rule.busyFromHour;
this.busyToHour = this.data.rule.busyToHour;
if (this.data.rule.busyWeekDays) {
this.availableFromDate = this.data.rule? this.data.rule.availableFromDate : null;
this.availableToDate = this.data.rule? this.data.rule.availableToDate : null;
this.isRemoteAvailable = this.data.rule? this.data.rule.isRemoteAvailable : false;
this.availableReason = this.data.rule? this.data.rule.availableReason : null;
this.busyFromHour = this.data.rule? this.data.rule.busyFromHour : null;
this.busyToHour = this.data.rule? this.data.rule.busyToHour : null;
if (this.data.rule && this.data.rule.busyWeekDays) {
this.busyWeekDays = this.data.rule.busyWeekDays.reduce((acc: {
[x: string]: boolean;
}, day: string | number) => {
@ -52,7 +52,7 @@ export class CreateCalendarRuleComponent {
return acc;
}, {});
}
this.ruleId = this.data.rule['@id']
this.ruleId = this.data.rule ? this.data.rule['@id'] : null
}
}

View File

@ -51,7 +51,6 @@ export class CreateCalendarComponent implements OnInit {
});
}
getBusyWeekDaysMap(indices: number[]): string[] {
console.log(indices)
const weekDays = ['Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado', 'Domingo'];

View File

@ -113,6 +113,7 @@
<p>{{ result.type !== 'client' ? result.type : '' }}</p>
<p>{{ result.type === 'client' ? result.ip : '' }}</p>
<p>{{ result.type === 'client' ? result.mac : '' }}</p>
<p>{{ result.type === 'client' ? result.status : '' }}</p>
<p *ngIf="result.type !== 'client'" i18n="@@internalUnits">
Unidades internas: {{ result.type !== 'client' ? result.children.length : 0 }}
</p>

View File

@ -410,4 +410,4 @@ export class AdvancedSearchComponent {
const dialogRef = this.dialog.open(AcctionsModalComponent, { data: { selectedElements: this.selectedElements }, width: '700px'});
}
}
}

View File

@ -52,6 +52,11 @@ export class ClientTabViewComponent {
header: 'Mac',
cell: (client: any) => `${client.mac}`
},
{
columnDef: 'status',
header: 'Estado',
cell: (client: any) => `${client.status}`
},
{
columnDef: 'organizationalUnit',
header: 'Pertenece a',

View File

@ -34,3 +34,14 @@
button{
margin-left: 10px;
}
.mat-chip-success {
background-color: #4CAF50 !important;
color: white !important;
}
.mat-chip-error {
background-color: #F44336 !important;
color: white !important;
}

View File

@ -27,9 +27,13 @@
<table mat-table [dataSource]="dataSource">
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
<td mat-cell *matCellDef="let image" >
<ng-container >
{{ column.cell(image) }}
<td mat-cell *matCellDef="let ou" >
<ng-container *ngIf="column.columnDef !== 'available'">
{{ column.cell(ou) }}
</ng-container>
<ng-container *ngIf="column.columnDef === 'available'" >
<mat-chip *ngIf="ou.available" class="mat-chip-success"><mat-icon style="color:white;">check</mat-icon></mat-chip>
<mat-chip *ngIf="!ou.available" class="mat-chip-error"> <mat-icon style="color:white;">close</mat-icon></mat-chip>
</ng-container>
</td>
</ng-container>

View File

@ -55,6 +55,16 @@ export class OrganizationalUnitTabViewComponent {
header: 'Tipo',
cell: (ou: any) => `${ou.type}`
},
{
columnDef: 'remoteCalendar',
header: 'Calendario',
cell: (ou: any) => `${ou.remoteCalendar ? ou.remoteCalendar.name : '-'}`
},
{
columnDef: 'available',
header: 'Aula Reservada',
cell: (ou: any) => `${ou.available ? 'No' : 'Si'}`
},
{
columnDef: 'createdAt',
header: 'Fecha de creación',
@ -134,7 +144,6 @@ export class OrganizationalUnitTabViewComponent {
this.getOrganizationalUnits();
}
search(): void {
this.loading = true;
this.getOrganizationalUnits()

View File

@ -49,10 +49,10 @@
<mat-label i18n="@@capacityLabel">Aforo</mat-label>
<input matInput formControlName="capacity" type="number">
</mat-form-field>
<mat-form-field appearance="fill">
<mat-form-field class="form-field" appearance="fill">
<mat-label>Calendario Asociado</mat-label>
<mat-select (selectionChange)="onCalendarChange($event)">
<mat-option *ngFor="let calendar of calendars" [value]="calendar.uuid">
<mat-select formControlName="remoteCalendar" (selectionChange)="onCalendarChange($event)">
<mat-option *ngFor="let calendar of calendars" [value]="calendar['@id']">
{{ calendar.name }}
</mat-option>
</mat-select>

View File

@ -74,14 +74,14 @@ export class CreateOrganizationalUnitComponent implements OnInit {
mcastMode: [''],
menu: [''],
hardwareProfile: [''],
validation: [false],
remoteCalendar: ['']
validation: [false]
});
this.classroomInfoFormGroup = this._formBuilder.group({
location: [''],
projector: [false],
board: [false],
capacity: [0, Validators.min(0)]
capacity: [0, Validators.min(0)],
remoteCalendar: ['']
});
}
@ -149,9 +149,9 @@ export class CreateOrganizationalUnitComponent implements OnInit {
}
private isFormValid(): boolean {
return this.generalFormGroup.valid &&
this.additionalInfoFormGroup.valid &&
this.networkSettingsFormGroup.valid &&
return this.generalFormGroup.valid &&
this.additionalInfoFormGroup.valid &&
this.networkSettingsFormGroup.valid &&
(this.generalFormGroup.value.type !== 'classroom' || this.classroomInfoFormGroup.valid);
}
@ -166,7 +166,6 @@ export class CreateOrganizationalUnitComponent implements OnInit {
...classroomInfoFormValues,
comments: additionalInfoFormValues.comments,
networkSettings: { ...networkSettingsFormValues },
remoteCalendar: this.selectedCalendarUuid ? `/remote-calendars/${this.selectedCalendarUuid}` : null,
menu: networkSettingsFormValues.menu || null,
hardwareProfile: networkSettingsFormValues.hardwareProfile || null,
};

View File

@ -46,12 +46,10 @@
<input matInput formControlName="capacity" type="number">
</mat-form-field>
<!-- Campo nuevo para seleccionar el calendario asociado -->
<p>Calendario asociado actual {{currentCalendar.name}}</p>
<mat-form-field appearance="fill">
<mat-form-field class="form-field" appearance="fill">
<mat-label>Calendario Asociado</mat-label>
<mat-select formControlName="calendarSelect" (selectionChange)="onCalendarChange($event)">
<mat-option *ngFor="let calendar of calendars" [value]="calendar.uuid">
<mat-select formControlName="remoteCalendar" (selectionChange)="onCalendarChange($event)">
<mat-option *ngFor="let calendar of calendars" [value]="calendar['@id']">
{{ calendar.name }}
</mat-option>
</mat-select>
@ -63,7 +61,7 @@
</div>
</form>
</mat-step>
<!-- Step 3: Información Adicional -->
<mat-step [stepControl]="additionalInfoFormGroup">

View File

@ -19,9 +19,9 @@ export class EditOrganizationalUnitComponent implements OnInit {
networkSettingsFormGroup: FormGroup;
classroomInfoFormGroup: FormGroup;
types: string[] = ['organizational-unit', 'classrooms-group', 'classroom', 'clients-group'];
parentUnits: any[] = [];
parentUnits: any[] = [];
hardwareProfiles: any[] = [];
isEditMode: boolean;
isEditMode: boolean;
currentCalendar: any = [];
protected p2pModeOptions = [
{"name": 'Leecher', "value": "p2p-mode-leecher"},
@ -32,18 +32,18 @@ export class EditOrganizationalUnitComponent implements OnInit {
{"name": 'Half duplex', "value": "half-duplex"},
{"name": 'Full duplex', "value": "full-duplex"},
];
@Output() unitAdded = new EventEmitter();
@Output() unitAdded = new EventEmitter();
calendars: any;
constructor(
private _formBuilder: FormBuilder,
private dialogRef: MatDialogRef<CreateOrganizationalUnitComponent>,
private http: HttpClient,
private http: HttpClient,
private dataService: DataService,
private toastService: ToastrService,
@Inject(MAT_DIALOG_DATA) public data: any
@Inject(MAT_DIALOG_DATA) public data: any
) {
this.isEditMode = !!data?.uuid;
this.isEditMode = !!data?.uuid;
this.generalFormGroup = this._formBuilder.group({
name: ['', Validators.required],
@ -78,7 +78,7 @@ export class EditOrganizationalUnitComponent implements OnInit {
projector: [false],
board: [false],
capacity: [0, Validators.min(0)],
calendarSelect: [null]
remoteCalendar: [null]
});
if (this.isEditMode) {
@ -130,7 +130,7 @@ export class EditOrganizationalUnitComponent implements OnInit {
loadCurrentCalendar(uuid: string): void {
const apiUrl = `${this.baseUrl}/remote-calendars/${uuid}`;
this.http.get<any>(apiUrl).subscribe(
response => this.currentCalendar = response,
response => this.currentCalendar = response,
error => {
console.error('Error loading current calendar', error);
this.openSnackBar(true, 'Error loading current calendar');
@ -140,7 +140,7 @@ export class EditOrganizationalUnitComponent implements OnInit {
onCalendarChange(event: any) {
const selectedCalendarId = event.value;
console.log('Selected calendar ID:', selectedCalendarId);
// Aquí puedes agregar la lógica adicional para manejar el cambio de calendario si es necesario
this.generalFormGroup.value.remoteCalendar = selectedCalendarId;
}
loadData(uuid: string) {
@ -179,27 +179,30 @@ export class EditOrganizationalUnitComponent implements OnInit {
projector: data.projector,
board: data.board,
capacity: data.capacity,
calendarSelect: data.remoteCalendar['@id']
remoteCalendar: data.remoteCalendar ? data.remoteCalendar['@id'] : null
});
this.loadCurrentCalendar(data.remoteCalendar.uuid);
},
error => {
console.error('Error fetching data for edit:', error);
this.openSnackBar(true, 'Error al cargar la unidad organizativa: ' + error.error['hydra:description'])
this.onNoClick()
}
);
console.log(this.classroomInfoFormGroup.value);
}
onSubmit() {
if (this.generalFormGroup.valid && this.additionalInfoFormGroup.valid && this.networkSettingsFormGroup.valid) {
const parentValue = this.generalFormGroup.value.parent;
console.log(this.generalFormGroup.value.remoetCalendar);
const formData = {
name: this.generalFormGroup.value.name,
parent: parentValue || null,
description: this.generalFormGroup.value.description,
comments: this.additionalInfoFormGroup.value.comments,
remoteCalendar: this.generalFormGroup.value.remoteCalendar,
type: this.generalFormGroup.value.type,
networkSettings: this.networkSettingsFormGroup.value
};