From 97a9dc03ed7c2f2172347c43f741109544ea9274 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rodr=C3=ADguez?= <diego.rodriguez@rootstrap.com> Date: Fri, 29 Nov 2024 11:48:03 -0300 Subject: [PATCH 1/9] Groups wip create groups and manage users --- .../app/layout/grupos/grupos.component.html | 68 ++--- .../src/app/layout/grupos/grupos.component.ts | 136 +++++++--- .../src/app/layout/layout.module.ts | 2 + .../create-group-modal.component.html | 33 +++ .../create-group-modal.component.ts | 38 +++ .../src/app/shared/components/index.ts | 2 + .../manage-group-users-modal.component.html | 49 ++++ .../manage-group-users-modal.component.ts | 74 ++++++ Frontend Angular 4/src/app/shared/config.ts | 6 + .../src/app/shared/objects/grupo.ts | 32 +-- .../app/shared/services/document.service.ts | 6 +- .../src/app/shared/services/file.service.ts | 1 - .../src/app/shared/services/group.service.ts | 233 ++++++++++++++++++ .../app/shared/services/haskell.service.ts | 6 +- .../app/shared/services/session.service.ts | 6 +- .../src/app/shared/shared.module.ts | 4 + Frontend Angular 4/src/assets/i18n/en.json | 7 +- Frontend Angular 4/src/assets/i18n/es.json | 7 +- 18 files changed, 614 insertions(+), 96 deletions(-) create mode 100644 Frontend Angular 4/src/app/shared/components/create-group-modal/create-group-modal.component.html create mode 100644 Frontend Angular 4/src/app/shared/components/create-group-modal/create-group-modal.component.ts create mode 100644 Frontend Angular 4/src/app/shared/components/manage-group-users-modal/manage-group-users-modal.component.html create mode 100644 Frontend Angular 4/src/app/shared/components/manage-group-users-modal/manage-group-users-modal.component.ts create mode 100644 Frontend Angular 4/src/app/shared/services/group.service.ts diff --git a/Frontend Angular 4/src/app/layout/grupos/grupos.component.html b/Frontend Angular 4/src/app/layout/grupos/grupos.component.html index ceb2c6c..407b509 100755 --- a/Frontend Angular 4/src/app/layout/grupos/grupos.component.html +++ b/Frontend Angular 4/src/app/layout/grupos/grupos.component.html @@ -13,8 +13,20 @@ <div class="row" style="margin-top: 20px"> <div class="col-lg-5"> <div class="card" *ngIf="grupoSeleccionado == undefined"> - <div class="card-header"> + <div class="card-header flex justify-between"> <div *ngIf="grupoSeleccionado == undefined">Grupos</div> + <button + ngbPopover="{{ 'i18n.action.new' | translate | titleCase }}" + triggers="mouseenter:mouseleave" + placement="bottom" + type="button" + class="btn btn-sm btn-secondary" + aria-haspopup="true" + aria-expanded="false" + (click)="openCreateGroupModal(false)" + > + <i aria-hidden="true" class="fa fa-plus"></i> + </button> </div> <div class="card-block" *ngIf="grupoSeleccionado == undefined"> <div @@ -34,7 +46,7 @@ > <i class="fa fa-users matefun-fa-user" aria-hidden="true"></i> <p> - {{ grupo.grado + "°" + grupo.grupo + " - " + grupo.anio }} + {{ grupo.name }} </p> </div> </div> @@ -64,17 +76,23 @@ > <i class="fa fa-arrow-up"></i> </button> + <button + class="btn btn-sm btn-secondary pull-right" + style="cursor: pointer; margin-top: -35px; margin-right: 36px" + (click)="openManageGroupUsersModal()" + ngbPopover="{{ + 'i18n.msg.group.manageGroupUsers' | translate | titleCase + }}" + placement="bottom" + triggers="mouseenter:mouseleave" + > + <i class="fa fa-users"></i> + </button> <p class="pull-right" - style="margin-top: -34px; margin-right: 60px" + style="margin-top: -34px; margin-right: 93px" > - {{ - grupoSeleccionado.grado + - "°" + - grupoSeleccionado.grupo + - " - " + - grupoSeleccionado.anio - }} + {{ grupoSeleccionado.name }} </p> </div> <div class="card-block"> @@ -83,8 +101,8 @@ style="min-height: 100px; overflow-y: scroll" > <div - *ngFor="let alumno of grupoSeleccionado.alumnos" - (click)="seleccionarAlumno(alumno)" + *ngFor="let user of grupoSeleccionado.users" + (click)="seleccionarUser(user)" class="col-sm-3 matefun-group-wrapper" > <i @@ -92,7 +110,7 @@ aria-hidden="true" ></i> <p> - {{ alumno.apellido + ", " + alumno.nombre }} + {{ user.username }} </p> </div> </div> @@ -121,13 +139,7 @@ class="pull-right" style="margin-top: -34px; margin-right: 60px" > - {{ - grupoSeleccionado.grado + - "°" + - grupoSeleccionado.grupo + - " - " + - grupoSeleccionado.anio - }} + {{ grupoSeleccionado.name }} </p> </div> <div class="card-block"> @@ -156,14 +168,14 @@ <div [ngbNavOutlet]="nav" class="mt-2" *ngIf="grupoSeleccionado"></div> </div> <div class="col-lg-7"> - <div class="card" *ngIf="alumnoSeleccionado"> + <div class="card" *ngIf="selectedUser"> <div class="card-block"> <div class="row listadoEntregasAlumnoGrupos" style="min-height: 100px; overflow-y: scroll" > <div - *ngFor="let entrega of alumnoSeleccionado.archivos" + *ngFor="let entrega of selectedUser.archivos" (click)="seleccionarEntrega(entrega)" class="col-sm-3 col-4 matefun-file-wrapper" > @@ -175,7 +187,7 @@ <p>{{ entrega.nombre }}</p> </div> <div - *ngIf="alumnoSeleccionado.archivos.length == 0" + *ngIf="selectedUser.archivos.length == 0" style="width: 100%; text-align: center" > <i @@ -187,10 +199,8 @@ class="fa fa-file-text" ></i> <p> - No hay entregas del alumno: - {{ - alumnoSeleccionado.nombre + " " + alumnoSeleccionado.apellido - }} + No hay entregas del usuario: + {{ selectedUser.username }} </p> </div> </div> @@ -198,9 +208,7 @@ </div> <div class="card" - *ngIf=" - alumnoSeleccionado == undefined && archivoSeleccionado == undefined - " + *ngIf="selectedUser == undefined && archivoSeleccionado == undefined" > <div class="card-block"> <div diff --git a/Frontend Angular 4/src/app/layout/grupos/grupos.component.ts b/Frontend Angular 4/src/app/layout/grupos/grupos.component.ts index a41d57e..11c633b 100755 --- a/Frontend Angular 4/src/app/layout/grupos/grupos.component.ts +++ b/Frontend Angular 4/src/app/layout/grupos/grupos.component.ts @@ -1,8 +1,8 @@ -import { Component } from "@angular/core"; +import { ChangeDetectorRef, Component } from "@angular/core"; import { Router } from "@angular/router"; import { Archivo, Evaluacion } from "../../shared/objects/archivo-types"; -import { Grupo } from "../../shared/objects/grupo"; +import { Group } from "../../shared/objects/grupo"; import { Usuario } from "../../shared/objects/usuario"; import { AuthenticationService } from "../../shared/services/authentication.service"; import { HaskellService } from "../../shared/services/haskell.service"; @@ -10,6 +10,10 @@ import { SessionService } from "../../shared/services/session.service"; import { NgbPopoverConfig, NgbPopover } from "@ng-bootstrap/ng-bootstrap"; import { NotificacionService } from "../../shared/services/notificacion.service"; import { TranslateService } from "@ngx-translate/core"; +import { GroupService } from "../../shared/services/group.service"; +import { NgbModal } from "@ng-bootstrap/ng-bootstrap"; +import { CreateGroupModal } from "../../shared/components/create-group-modal/create-group-modal.component"; +import { ManageGroupUsersModal } from "app/shared/components/manage-group-users-modal/manage-group-users-modal.component"; @Component({ selector: "grupos", @@ -17,10 +21,10 @@ import { TranslateService } from "@ngx-translate/core"; }) export class GruposComponent { archivos: Archivo[] = []; - grupos: Grupo[] = []; - grupoSeleccionado: Grupo = undefined; + grupos: Group[] = []; + grupoSeleccionado: Group = undefined; - alumnoSeleccionado: Usuario = undefined; + selectedUser: Usuario = undefined; archivoSeleccionado: Archivo = undefined; @@ -33,6 +37,8 @@ export class GruposComponent { configCodeMirror = JSON.parse(localStorage.getItem("codeMirrorConfig")); translateService: any; + createModalRef: any; + // - - - - - - - - - - - - Modal show confirm - - - - - - - - - - - - /** * Con `true` se renderiza el modal de calificar entrega. @@ -66,7 +72,10 @@ export class GruposComponent { private haskellService: HaskellService, private notifService: NotificacionService, private sessionService: SessionService, - public translate: TranslateService + public translate: TranslateService, + public groupService: GroupService, + private modalService: NgbModal, + private changeDetectorRef: ChangeDetectorRef ) { this.translateService = translate; this.directorioActual = {}; @@ -91,6 +100,8 @@ export class GruposComponent { ngOnInit() { // let cedula = this.authService.getUser().cedula; // cedula + this.confirmGroupCreation = this.confirmGroupCreation.bind(this); + this.confirmGroupUpdated = this.confirmGroupUpdated.bind(this); this.loading = true; // this.haskellService.getGrupos(cedula).subscribe( // (grupos) => { @@ -100,6 +111,19 @@ export class GruposComponent { // }, // (error) => console.log(error) // ); + this.loadGroups(); + } + + loadGroups(next?: () => void) { + this.groupService.getGroups().subscribe( + (grupos) => { + this.grupos = grupos.groups; + // this.ordenarGrupos(); + this.loading = false; + if (next) next(); + }, + (error) => console.log(error) + ); } showNotification(type: "error" | "success" | "warning", traslation: string) { @@ -116,9 +140,9 @@ export class GruposComponent { } ordenarArchivos() { - this.grupoSeleccionado.archivos = this.grupoSeleccionado.archivos.sort( - this.ordenarAlph - ); + // this.grupoSeleccionado.archivos = this.grupoSeleccionado.archivos.sort( + // this.ordenarAlph + // ); } //ordeno los archivos del alumno (los archivos entregados.) @@ -150,9 +174,9 @@ export class GruposComponent { } ordenarAlumnos() { - this.grupoSeleccionado.alumnos = this.grupoSeleccionado.alumnos.sort( - this.ordenarAlumnosF - ); + // this.grupoSeleccionado.alumnos = this.grupoSeleccionado.alumnos.sort( + // this.ordenarAlumnosF + // ); } seleccionarGrupo(grupo) { @@ -160,18 +184,18 @@ export class GruposComponent { this.ordenarAlumnos(); this.ordenarArchivos(); this.archivoSeleccionado = undefined; - this.alumnoSeleccionado = undefined; + this.selectedUser = undefined; } desseleccionarGrupo() { this.grupoSeleccionado = undefined; this.archivoSeleccionado = undefined; - this.alumnoSeleccionado = undefined; + this.selectedUser = undefined; } - seleccionarAlumno(alumno) { + seleccionarUser(alumno) { if (!(typeof alumno === "undefined")) { - this.alumnoSeleccionado = alumno; + this.selectedUser = alumno; this.ordenarArchivosAlumno(); this.archivoSeleccionado = undefined; } @@ -179,13 +203,13 @@ export class GruposComponent { seleccionarArchivo(archivo) { this.archivoSeleccionado = archivo; - this.alumnoSeleccionado = undefined; + this.selectedUser = undefined; this.tipoArchivo = "compartido"; } seleccionarEntrega(entrega) { this.archivoSeleccionado = entrega; - this.alumnoSeleccionado = undefined; + this.selectedUser = undefined; this.tipoArchivo = "entrega"; } @@ -195,6 +219,60 @@ export class GruposComponent { this.modalQualifyDeliveryOpened = true; } + openCreateGroupModal() { + this.createModalRef = this.modalService.open(CreateGroupModal); + + this.createModalRef.componentInstance.modalTitle = "Nuevo Grupo"; + this.createModalRef.componentInstance.confirmGroupCreation = + this.confirmGroupCreation; + } + + confirmGroupCreation(nombre: string) { + if (nombre == undefined || nombre == "") { + this.showNotification("error", "i18n.warning.group.invalidName"); + return; + } + + /** Expresión regular para chequear que el nombre esté empiece con mayúscula */ + var regex = /^[A-Z]/; + if (!regex.test(nombre)) { + this.showNotification("error", "i18n.warning.group.capitalLetter"); + return; + } + + this.groupService.createGroup(nombre).subscribe( + (data) => { + this.loadGroups(); + + this.grupoSeleccionado = data.body["group"]; + this.createModalRef.close(); + }, + (error) => { + this.notifService.error(error); + } + ); + } + + confirmGroupUpdated() { + this.loadGroups(() => { + this.grupoSeleccionado = this.grupos.find( + (grupo) => grupo.id == this.grupoSeleccionado.id + ); + }); + } + + openManageGroupUsersModal() { + this.createModalRef = this.modalService.open(ManageGroupUsersModal); + + this.createModalRef.componentInstance.modalTitle = + "Administrar usuarios del grupo"; + this.createModalRef.componentInstance.groupName = + this.grupoSeleccionado.name; + this.createModalRef.componentInstance.groupId = this.grupoSeleccionado.id; + this.createModalRef.componentInstance.confirmGroupUpdated = + this.confirmGroupUpdated; + } + confirmFileQualify(event: CustomEvent<any>) { const { descripcion, estado, nota } = event.detail; @@ -234,17 +312,17 @@ export class GruposComponent { } esArchivoGrupo() { - if ( - this.archivoSeleccionado && - this.grupoSeleccionado && - this.grupoSeleccionado.archivos.some( - (arch) => arch.id == this.archivoSeleccionado.id - ) - ) { - return true; - } else { - return false; - } + // if ( + // this.archivoSeleccionado && + // this.grupoSeleccionado && + // this.grupoSeleccionado.archivos.some( + // (arch) => arch.id == this.archivoSeleccionado.id + // ) + // ) { + // return true; + // } else { + // return false; + // } } cargarArchivoCompartido() { diff --git a/Frontend Angular 4/src/app/layout/layout.module.ts b/Frontend Angular 4/src/app/layout/layout.module.ts index 5159725..3e4f59a 100755 --- a/Frontend Angular 4/src/app/layout/layout.module.ts +++ b/Frontend Angular 4/src/app/layout/layout.module.ts @@ -10,6 +10,7 @@ import { AuthenticationService } from "../shared/services/authentication.service import { HaskellService } from "../shared/services/haskell.service"; import { DocumentService } from "../shared/services/document.service"; import { FileService } from "../shared/services/file.service"; +import { GroupService } from "../shared/services/group.service"; import { LtCodemirrorModule } from "lt-codemirror"; import { CodemirrorModule } from "@ctrl/ngx-codemirror"; import { NotificacionModule } from "../notificacion/notificacion.module"; @@ -34,6 +35,7 @@ import { TitleCaseModule } from "../shared/modules/titlecase.module"; HaskellService, DocumentService, FileService, + GroupService, ], }) export class LayoutModule {} diff --git a/Frontend Angular 4/src/app/shared/components/create-group-modal/create-group-modal.component.html b/Frontend Angular 4/src/app/shared/components/create-group-modal/create-group-modal.component.html new file mode 100644 index 0000000..b49e958 --- /dev/null +++ b/Frontend Angular 4/src/app/shared/components/create-group-modal/create-group-modal.component.html @@ -0,0 +1,33 @@ +<div class="modal-header"> + <span class="modal-title font-bold" id="modal-title">{{ modalTitle }}</span> + <button + aria-label="Cerrar diálogo" + (click)="modal.dismiss('Close create group modal')" + part="close-button" + class="close-button fa fa-close" + type="button" + ></button> +</div> +<div class="modal-body"> + <div class="flex flex-col space-between"> + <label for="document-title">Group Name</label> + <input + type="text" + [(ngModel)]="groupName" + placeholder="Enter group name" + ngbAutofocus + id="group-name" + class="name-input border-opacity-25 border-black border-2 rounded w-full px-2 py-3 text-base focus-visible:border-focused-blue focus:outline-transparent" + /> + </div> + <div class="flex justify-end mt-4"> + <button + (click)="confirmGroupCreation(groupName)" + [disabled]="!groupName" + class="btn btn-primary" + slot="primary" + > + Crear Grupo + </button> + </div> +</div> diff --git a/Frontend Angular 4/src/app/shared/components/create-group-modal/create-group-modal.component.ts b/Frontend Angular 4/src/app/shared/components/create-group-modal/create-group-modal.component.ts new file mode 100644 index 0000000..05cba8d --- /dev/null +++ b/Frontend Angular 4/src/app/shared/components/create-group-modal/create-group-modal.component.ts @@ -0,0 +1,38 @@ +import { + Component, + ChangeDetectionStrategy, + Input, + OnInit, +} from "@angular/core"; + +import { NgbActiveModal } from "@ng-bootstrap/ng-bootstrap"; + +import { MFile } from "../../objects/archivo-types"; +import { Group } from "../../objects/grupo"; +import { FileService } from "../../services/file.service"; +import { GroupService } from "../../services/group.service"; + +@Component({ + selector: "app-create-group-modal", + templateUrl: "./create-group-modal.component.html", + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class CreateGroupModal implements OnInit { + @Input() modalTitle: string; + @Input() groupName: string; + + /** + * Se dispara cuando se confirma la creación del archivo en el directorio actual. + */ + @Input() confirmGroupCreation: (title: string) => void; + + constructor( + public modal: NgbActiveModal, + private groupService: GroupService + ) {} + + ngOnInit(): void { + this.groupName = `${this.groupName || ""}`; + this.modalTitle = `${this.modalTitle}`; + } +} diff --git a/Frontend Angular 4/src/app/shared/components/index.ts b/Frontend Angular 4/src/app/shared/components/index.ts index 381d0ed..0612484 100755 --- a/Frontend Angular 4/src/app/shared/components/index.ts +++ b/Frontend Angular 4/src/app/shared/components/index.ts @@ -8,3 +8,5 @@ export * from "./share-file-modal/share-file-modal.component"; export * from "./chip-input/chip-input.component"; export * from "./select/select.component"; export * from "./add-users-modal/add-users-modal.component"; +export * from "./create-group-modal/create-group-modal.component"; +export * from "./manage-group-users-modal/manage-group-users-modal.component"; diff --git a/Frontend Angular 4/src/app/shared/components/manage-group-users-modal/manage-group-users-modal.component.html b/Frontend Angular 4/src/app/shared/components/manage-group-users-modal/manage-group-users-modal.component.html new file mode 100644 index 0000000..7608fa9 --- /dev/null +++ b/Frontend Angular 4/src/app/shared/components/manage-group-users-modal/manage-group-users-modal.component.html @@ -0,0 +1,49 @@ +<app-add-users-modal + [modalTitle]="modalTitle" + [(chips)]="chips" + [newUserRolesEnum]="newUsersPermissionsEnum" + [newUserRole]="newUsersPermission" + (close)="modal.dismiss('Close manage user\'s group modal')" + [users]="usersCopy" + [rolesField]="groupRole" + [requiredRole]="'Owner'" + [save]="save" +></app-add-users-modal> + +<!-- <div class="modal-header"> + <span class="modal-title font-bold" id="modal-title">{{ modalTitle }}</span> + <button + aria-label="Cerrar diálogo" + (click)="modal.dismiss('Close share file modal')" + part="close-button" + class="close-button fa fa-close" + type="button" + ></button> +</div> +<div class="modal-body"> + <div class="flex flex-col justify-between w-full"> + <label for="new-users">New Users</label> + <div class="flex justify-between w-full items-center"> + <app-chip-input + [(chips)]="chips" + [placeholder]="'Add a new user'" + [id]="'new-users'" + class="w-full" + ></app-chip-input> + <app-select + [options]="newUsersPermissionsEnum" + [(selectedOption)]="newUsersPermission" + ></app-select> + </div> + </div> + <div class="flex justify-end"> + <button + (click)="confirmFileCreation(documentTitle, currentFolder.id)" + [disabled]="!documentTitle || !currentFolder" + class="btn btn-primary" + slot="primary" + > + Guardar + </button> + </div> +</div> --> diff --git a/Frontend Angular 4/src/app/shared/components/manage-group-users-modal/manage-group-users-modal.component.ts b/Frontend Angular 4/src/app/shared/components/manage-group-users-modal/manage-group-users-modal.component.ts new file mode 100644 index 0000000..22303ff --- /dev/null +++ b/Frontend Angular 4/src/app/shared/components/manage-group-users-modal/manage-group-users-modal.component.ts @@ -0,0 +1,74 @@ +import { + Component, + ChangeDetectionStrategy, + Input, + OnInit, + ChangeDetectorRef, +} from "@angular/core"; + +import { NgbActiveModal } from "@ng-bootstrap/ng-bootstrap"; + +import { User } from "../../objects/archivo-types"; +import { Group } from "../../objects/grupo"; +import { GroupService } from "../../services/group.service"; + +@Component({ + selector: "app-manage-group-users-modal", + templateUrl: "./manage-group-users-modal.component.html", + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ManageGroupUsersModal implements OnInit { + @Input() modalTitle: string; + @Input() groupName: string; + @Input() groupId: number; + group: Group; + newUsersPermissionsEnum: string[] = [ + "Owner", + "Manager", + "Moderator", + "Member", + ]; + newUsersPermission = this.newUsersPermissionsEnum[3]; + users: User[] = []; + usersCopy: User[] = []; + groupRole = "role"; + /** + * Se dispara cuando se quiere administrar los usuarios del grupo + */ + @Input() confirmGroupUpdated: () => void; + + // Hacer get del grupo al inicializar el modal + // Capaz mostrar un loader mientras ocurre + // Con eso mostrar los usuarios que tienen acceso al grupo y sus permisos + // Y usar el group_id para hacer el share + + chips: string[] = []; + + constructor( + public modal: NgbActiveModal, + private groupService: GroupService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + ngOnInit(): void { + this.groupService.getGroup(this.groupId).subscribe((groupResponse) => { + this.group = groupResponse.group; + this.users = groupResponse.group.users; + this.usersCopy = [...this.users]; + this.changeDetectorRef.detectChanges(); + }); + this.groupName = `${this.groupName}`; + this.modalTitle = `${this.modalTitle}`; + this.save = this.save.bind(this); + } + + save(userData: { id?: number; username: string; role: string }[]): void { + this.groupService.updateGroup(this.group.id, userData).subscribe({ + next: (_) => { + this.confirmGroupUpdated(); + this.modal.close(); + }, + error: (error) => console.log(error), + }); + } +} diff --git a/Frontend Angular 4/src/app/shared/config.ts b/Frontend Angular 4/src/app/shared/config.ts index f93b27e..1a9848d 100755 --- a/Frontend Angular 4/src/app/shared/config.ts +++ b/Frontend Angular 4/src/app/shared/config.ts @@ -50,3 +50,9 @@ export const CREATE_DOCUMENT = SERVER + "/api/v2/documents"; export const UPDATE_DOCUMENT = SERVER + "/api/v2/documents"; export const DESTROY_AUX_DOCUMENT = SERVER + "/api/v2/aux_documents"; + +export const GET_GROUP = SERVER + "/api/v2/groups/:id"; +export const GET_GROUPS = SERVER + "/api/v2/groups"; +export const CREATE_GROUP = SERVER + "/api/v2/groups"; +export const UPDATE_GROUP = SERVER + "/api/v2/groups"; +export const DELETE_GROUP = SERVER + "/api/v2/groups"; diff --git a/Frontend Angular 4/src/app/shared/objects/grupo.ts b/Frontend Angular 4/src/app/shared/objects/grupo.ts index 9c9679e..157e4e4 100755 --- a/Frontend Angular 4/src/app/shared/objects/grupo.ts +++ b/Frontend Angular 4/src/app/shared/objects/grupo.ts @@ -1,27 +1,9 @@ -import { Archivo } from "./archivo-types"; -import { Usuario } from "./usuario"; +import { MFile } from "./archivo-types"; +import { User } from "./archivo-types"; -export class Grupo { - anio: number; - grado: number; - grupo: string; - liceoId: number; - archivos: Archivo[]; - alumnos: Usuario[]; - - constructor( - anio: number, - grado: number, - grupo: string, - liceoId: number, - archivos: Archivo[], - alumnos: Usuario[] - ) { - this.anio = anio; - this.grado = grado; - this.grupo = grupo; - this.liceoId = liceoId; - this.archivos = archivos; - this.alumnos = alumnos; - } +export class Group { + id: number; + name: string; + files?: MFile[]; + users?: User[]; } diff --git a/Frontend Angular 4/src/app/shared/services/document.service.ts b/Frontend Angular 4/src/app/shared/services/document.service.ts index e1df524..9883de7 100644 --- a/Frontend Angular 4/src/app/shared/services/document.service.ts +++ b/Frontend Angular 4/src/app/shared/services/document.service.ts @@ -13,7 +13,7 @@ import { MFile, MDocument, } from "../objects/archivo-types"; -import { Grupo } from "../objects/grupo"; +import { Group } from "../objects/grupo"; import { SERVER } from "../config"; import { TranslateService } from "@ngx-translate/core"; @@ -262,14 +262,14 @@ export class DocumentService { } // Legacy method, need to check if it's still used - getGrupos(cedula: string): Observable<Grupo[]> { + getGrupos(cedula: string): Observable<Group[]> { let headers = this.getHeaders(); let params: HttpParams = new HttpParams(); params = params.set("cedula", cedula); let httpOptions = { headers: headers, params: params }; return this.http - .get<Grupo[]>(SERVER + "/servicios/grupo", httpOptions) + .get<Group[]>(SERVER + "/servicios/grupo", httpOptions) .pipe(catchError(this.handleError)); } diff --git a/Frontend Angular 4/src/app/shared/services/file.service.ts b/Frontend Angular 4/src/app/shared/services/file.service.ts index b900b57..f1d56c3 100644 --- a/Frontend Angular 4/src/app/shared/services/file.service.ts +++ b/Frontend Angular 4/src/app/shared/services/file.service.ts @@ -8,7 +8,6 @@ import { HttpResponse, } from "@angular/common/http"; import { Archivo, Evaluacion, MFile } from "../objects/archivo-types"; -import { Grupo } from "../objects/grupo"; import { SERVER } from "../config"; import { TranslateService } from "@ngx-translate/core"; diff --git a/Frontend Angular 4/src/app/shared/services/group.service.ts b/Frontend Angular 4/src/app/shared/services/group.service.ts new file mode 100644 index 0000000..bc32858 --- /dev/null +++ b/Frontend Angular 4/src/app/shared/services/group.service.ts @@ -0,0 +1,233 @@ +import { throwError as observableThrowError, Observable } from "rxjs"; +import { Injectable } from "@angular/core"; +import { Router } from "@angular/router"; +import { + HttpClient, + HttpHeaders, + HttpParams, + HttpResponse, +} from "@angular/common/http"; +import { Archivo, Evaluacion, MFile } from "../objects/archivo-types"; +import { Group } from "../objects/grupo"; + +import { SERVER } from "../config"; +import { TranslateService } from "@ngx-translate/core"; +import { AuthenticationService } from "./authentication.service"; +import { catchError } from "rxjs/operators"; +import { + CREATE_GROUP, + UPDATE_GROUP, + DELETE_GROUP, + GET_GROUP, + GET_GROUPS, +} from "../config"; + +@Injectable() +export class GroupService { + translateService: any; + + /** + * Creates a new GroupService with the injected HttpClient. + * @param {HttpClient} http - The injected HttpClient. + * @constructor + */ + constructor( + private http: HttpClient, + private router: Router, + private authService: AuthenticationService, + public translate: TranslateService + ) { + this.translateService = translate; + } + + private getHeaders() { + return new HttpHeaders({ + "Content-Type": "application/json", + Authorization: "Bearer " + this.authService.getToken(), + }); + } + + private postHeaders() { + return new HttpHeaders({ + "Content-Type": "application/json", + Accept: "application/json", + "Access-Control-Allow-Headers": "Content-Type", + }); + } + + getGroup(id: number): Observable<{ group: Group }> { + return this.http + .get<{ group: Group }>(GET_GROUP.replace(/:id/g, String(id)), { + headers: this.getHeaders(), + }) + .pipe(catchError(this.handleError)); + } + + getGroups(): Observable<{ groups: Group[] }> { + return this.http + .get<{ groups: Group[] }>(GET_GROUPS, { + headers: this.getHeaders(), + }) + .pipe(catchError(this.handleError)); + } + + // getFilesList(options = {}): Observable<{ files: MFile[] }> { + // const siblingsOfId = options["siblingsOfId"]; + // return this.http + // .get<{ files: MFile[] }>(GET_FILES_LISTS, { + // headers: this.getHeaders(), + // params: siblingsOfId + // ? new HttpParams().set("siblings_of_id", siblingsOfId) + // : null, + // }) + // .pipe(catchError(this.handleError)); + // } + + fileToArchivo(file: MFile, parent: Archivo = null): Archivo { + let archivo = new Archivo(); + archivo; + archivo.id = file.id; + archivo.documentId = file.document_id; + archivo.nombre = file.title; + archivo.contenido = ""; + archivo.fechaCreacion = file.created_at; + archivo.cedulaCreador = file.users[0]?.email; + archivo.editable = true; + archivo.padreId = + file.ancestry === "/" + ? -1 + : parseInt(file.ancestry.slice(0, -1).split("/").pop()); + archivo.archivos = []; + archivo.parent = parent; + archivo.archivoOrigenId = null; + archivo.directorio = file.directory; + archivo.estado = "activo"; + archivo.eliminado = false; + archivo.evaluacion = null; + archivo.users = file.users; + archivo.role = file.role; + + if (!!file.children) { + archivo.archivos = file.children.map<Archivo>((child) => { + return this.fileToArchivo(child, archivo); + }); + } + + return archivo; + } + + createGroup(name: string): Observable<HttpResponse<Object>> { + return this.http.post<Object>( + CREATE_GROUP, + { + group: { + name: name, + }, + }, + { + observe: "response", + headers: this.postHeaders(), + } + ); + } + + archivoToFile(archivo: Archivo, parent: MFile = null): MFile { + let file = { + id: archivo.id, + document_id: archivo.documentId, + title: archivo.nombre, + created_at: archivo.fechaCreacion, + parent_id: archivo.padreId, + parent: parent, + children: [], + directory: archivo.directorio, + role: archivo.role, + }; + + file.children = archivo.archivos.map<MFile>((archivoHijo) => { + return this.archivoToFile(archivoHijo, file); + }); + + return file; + } + + completeFiles(file: MFile, parent: MFile = null): MFile { + file.parent = parent; + file.children.forEach((child) => { + this.completeFiles(child, file); + }); + + return file; + } + + filterOnlyDirectories(file: MFile, parent: MFile = null): MFile { + let newFile = { + id: file.id, + document_id: file.document_id, + title: file.title, + created_at: file.created_at, + parent_id: file.parent_id, + parent: parent, + children: [], + directory: file.directory, + }; + + newFile.children = file.children + .filter((child) => child.directory) + .map((child) => this.filterOnlyDirectories(child, newFile)); + + return newFile; + } + + updateGroup(group_id, users): Observable<HttpResponse<Object>> { + const update_params = {}; + if (users) { + users = users.map((user) => { + return { ...user, role: user.role.toLowerCase() }; + }); + update_params["users"] = users; + } + + return this.http.patch<Object>( + `${UPDATE_GROUP}/${group_id}`, + { group: update_params }, + { + observe: "response", + headers: this.postHeaders(), + } + ); + } + + // deleteFile(file_id): Observable<HttpResponse<Object>> { + // return this.http.delete<Object>(`${DELETE_FILE}/${file_id}`, { + // observe: "response", + // headers: this.postHeaders(), + // }); + // } + + // destroyAuxFile(): void { + // this.http.delete<Object>(DESTROY_AUX_DOCUMENT, { + // headers: this.postHeaders(), + // }); + // } + + /** + * Handle HTTP error + */ + private handleError(error: any) { + if (error.status == 401) { + this.translateService + .get("i18n.code") + .subscribe((res) => this.router.navigate(["/" + res + "/login"])); + } + // In a real world app, we might use a remote logging infrastructure + // We'd also dig deeper into the error to get a better message + let errMsg = error.message + ? error.message + : error.status + ? `${error.status} - ${error.statusText}` + : "Server error"; + console.error(errMsg); // log to console instead + return observableThrowError(errMsg); + } +} diff --git a/Frontend Angular 4/src/app/shared/services/haskell.service.ts b/Frontend Angular 4/src/app/shared/services/haskell.service.ts index 5702c47..7d086ca 100755 --- a/Frontend Angular 4/src/app/shared/services/haskell.service.ts +++ b/Frontend Angular 4/src/app/shared/services/haskell.service.ts @@ -3,7 +3,7 @@ import { Injectable } from "@angular/core"; import { Router } from "@angular/router"; import { HttpClient, HttpHeaders, HttpParams } from "@angular/common/http"; import { Archivo, Evaluacion } from "../objects/archivo-types"; -import { Grupo } from "../objects/grupo"; +import { Group } from "../objects/grupo"; import { SERVER } from "../config"; import { TranslateService } from "@ngx-translate/core"; @@ -145,14 +145,14 @@ export class HaskellService { .pipe(catchError(this.handleError)); } - getGrupos(cedula: string): Observable<Grupo[]> { + getGrupos(cedula: string): Observable<Group[]> { let headers = this.getHeaders(); let params: HttpParams = new HttpParams(); params = params.set("cedula", cedula); let httpOptions = { headers: headers, params: params }; return this.http - .get<Grupo[]>(SERVER + "/servicios/grupo", httpOptions) + .get<Group[]>(SERVER + "/servicios/grupo", httpOptions) .pipe(catchError(this.handleError)); } diff --git a/Frontend Angular 4/src/app/shared/services/session.service.ts b/Frontend Angular 4/src/app/shared/services/session.service.ts index 697c1a7..4ce6bfb 100755 --- a/Frontend Angular 4/src/app/shared/services/session.service.ts +++ b/Frontend Angular 4/src/app/shared/services/session.service.ts @@ -1,6 +1,6 @@ import { Injectable } from "@angular/core"; import { Archivo, MFile, MDocument } from "../objects/archivo-types"; -import { Grupo } from "../objects/grupo"; +import { Group } from "../objects/grupo"; @Injectable() export class SessionService { @@ -11,7 +11,7 @@ export class SessionService { directorioActual: any; archivosCompartidos: any; file: MFile; - grupos: Grupo[]; + grupos: Group[]; currentDirectoryDocuments: MDocument[]; public setArchivo(archivo) { @@ -40,7 +40,7 @@ export class SessionService { return this.file; } - public setGrupos(grupos: Grupo[]) { + public setGrupos(grupos: Group[]) { this.grupos = grupos; } diff --git a/Frontend Angular 4/src/app/shared/shared.module.ts b/Frontend Angular 4/src/app/shared/shared.module.ts index 73c823e..8fcf224 100644 --- a/Frontend Angular 4/src/app/shared/shared.module.ts +++ b/Frontend Angular 4/src/app/shared/shared.module.ts @@ -4,8 +4,10 @@ import { TitleCaseModule } from "../shared/modules/titlecase.module"; import { I18nModule } from "../shared/modules/translate/i18n.module"; import { CommonModule } from "@angular/common"; import { CreateFileModal } from "./components"; +import { CreateGroupModal } from "./components"; import { ImportFileModal } from "./components"; import { ShareFileModal } from "./components"; +import { ManageGroupUsersModal } from "./components"; import { FormsModule } from "@angular/forms"; import { FolderInput } from "./components"; import { SelectComponent } from "./components"; @@ -30,9 +32,11 @@ import { AddUsersModal } from "./components"; declarations: [ MainButtonComponent, CreateFileModal, + CreateGroupModal, FolderInput, ImportFileModal, ShareFileModal, + ManageGroupUsersModal, ChipInputComponent, SelectComponent, AddUsersModal, diff --git a/Frontend Angular 4/src/assets/i18n/en.json b/Frontend Angular 4/src/assets/i18n/en.json index 75eebe9..af85514 100755 --- a/Frontend Angular 4/src/assets/i18n/en.json +++ b/Frontend Angular 4/src/assets/i18n/en.json @@ -134,6 +134,9 @@ "success": "Please check your email", "error": "An error occurred while sending the email" }, + "group": { + "manageGroupUsers": "Manage group users" + }, "updatePassword": { "success": "Your password has been updated, you will be redirected shortly", "error": "An error occurred while updating your password", @@ -159,7 +162,9 @@ "invalidName": "Invalid file name." }, "group": { - "select": "Select a group" + "select": "Select a group", + "capitalLetter": "Group name must start with upper case.", + "invalidName": "Invalid group name." } }, "shortcuts": { diff --git a/Frontend Angular 4/src/assets/i18n/es.json b/Frontend Angular 4/src/assets/i18n/es.json index 55e091b..2d195ce 100755 --- a/Frontend Angular 4/src/assets/i18n/es.json +++ b/Frontend Angular 4/src/assets/i18n/es.json @@ -134,6 +134,9 @@ "success": "Favor busque en su correo el email", "error": "Error al enviar el email" }, + "group": { + "manageGroupUsers": "Administrar usuarios del grupo" + }, "updatePassword": { "success": "Su contraseña ha sido actualizada, será redirigido en 5 segundos", "error": "Ha ocurrido un error al actualizar su contraseña", @@ -159,7 +162,9 @@ "invalidName": "Nombre de archivo inválido." }, "group": { - "select": "Seleccione un grupo" + "select": "Seleccione un grupo", + "capitalLetter": "Nombre del grupo debe iniciar con mayúscula.", + "invalidName": "Nombre de grupo inválido." } }, "shortcuts": { -- GitLab From 530419a7561b0ec9548df43131863d2e4570454c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rodr=C3=ADguez?= <diego.rodriguez@rootstrap.com> Date: Mon, 2 Dec 2024 18:27:07 -0300 Subject: [PATCH 2/9] group wip destroy grups complete --- .../app/layout/grupos/grupos.component.html | 16 ++++- .../src/app/layout/grupos/grupos.component.ts | 59 +++++++++++++++++++ .../action-confirmation-modal.component.html | 27 +++++++++ .../action-confirmation-modal.component.ts | 15 +++++ .../destroy-group-modal.component.html | 6 ++ .../destroy-group-modal.component.ts | 53 +++++++++++++++++ .../src/app/shared/components/index.ts | 2 + .../src/app/shared/objects/grupo.ts | 1 + .../src/app/shared/services/group.service.ts | 13 ++-- .../src/app/shared/shared.module.ts | 5 +- Frontend Angular 4/src/app/utils.ts | 8 +++ Frontend Angular 4/src/assets/i18n/en.json | 3 +- Frontend Angular 4/src/assets/i18n/es.json | 3 +- 13 files changed, 200 insertions(+), 11 deletions(-) create mode 100644 Frontend Angular 4/src/app/shared/components/action-confirmation-modal/action-confirmation-modal.component.html create mode 100644 Frontend Angular 4/src/app/shared/components/action-confirmation-modal/action-confirmation-modal.component.ts create mode 100644 Frontend Angular 4/src/app/shared/components/destroy-group-modal/destroy-group-modal.component.html create mode 100644 Frontend Angular 4/src/app/shared/components/destroy-group-modal/destroy-group-modal.component.ts diff --git a/Frontend Angular 4/src/app/layout/grupos/grupos.component.html b/Frontend Angular 4/src/app/layout/grupos/grupos.component.html index 407b509..9c374cb 100755 --- a/Frontend Angular 4/src/app/layout/grupos/grupos.component.html +++ b/Frontend Angular 4/src/app/layout/grupos/grupos.component.html @@ -77,6 +77,7 @@ <i class="fa fa-arrow-up"></i> </button> <button + *ngIf="roleCanManageUsers()" class="btn btn-sm btn-secondary pull-right" style="cursor: pointer; margin-top: -35px; margin-right: 36px" (click)="openManageGroupUsersModal()" @@ -88,9 +89,22 @@ > <i class="fa fa-users"></i> </button> + <button + *ngIf="roleCanDestroyGroup()" + class="btn btn-sm btn-secondary pull-right" + style="cursor: pointer; margin-top: -35px; margin-right: 71px" + (click)="openDestroyGroupModal()" + ngbPopover="{{ + 'i18n.msg.group.destroyGroupTooltip' | translate | titleCase + }}" + placement="bottom" + triggers="mouseenter:mouseleave" + > + <i class="fa fa-close"></i> + </button> <p class="pull-right" - style="margin-top: -34px; margin-right: 93px" + style="margin-top: -34px; margin-right: 128px" > {{ grupoSeleccionado.name }} </p> diff --git a/Frontend Angular 4/src/app/layout/grupos/grupos.component.ts b/Frontend Angular 4/src/app/layout/grupos/grupos.component.ts index 11c633b..d480280 100755 --- a/Frontend Angular 4/src/app/layout/grupos/grupos.component.ts +++ b/Frontend Angular 4/src/app/layout/grupos/grupos.component.ts @@ -11,9 +11,11 @@ import { NgbPopoverConfig, NgbPopover } from "@ng-bootstrap/ng-bootstrap"; import { NotificacionService } from "../../shared/services/notificacion.service"; import { TranslateService } from "@ngx-translate/core"; import { GroupService } from "../../shared/services/group.service"; +import { roleCanManageGroupUsers, roleCanDestroyGroup } from "app/utils"; import { NgbModal } from "@ng-bootstrap/ng-bootstrap"; import { CreateGroupModal } from "../../shared/components/create-group-modal/create-group-modal.component"; import { ManageGroupUsersModal } from "app/shared/components/manage-group-users-modal/manage-group-users-modal.component"; +import { DestroyGroupModal } from "app/shared"; @Component({ selector: "grupos", @@ -102,6 +104,8 @@ export class GruposComponent { // let cedula = this.authService.getUser().cedula; // cedula this.confirmGroupCreation = this.confirmGroupCreation.bind(this); this.confirmGroupUpdated = this.confirmGroupUpdated.bind(this); + this.confirmGroupDestroyed = this.confirmGroupDestroyed.bind(this); + this.loading = true; // this.haskellService.getGrupos(cedula).subscribe( // (grupos) => { @@ -213,6 +217,61 @@ export class GruposComponent { this.tipoArchivo = "entrega"; } + roleCanManageUsers(): boolean { + return ( + !!this.grupoSeleccionado && + roleCanManageGroupUsers(this.grupoSeleccionado.role) + ); + } + + roleCanDestroyGroup(): boolean { + return ( + !!this.grupoSeleccionado && + roleCanDestroyGroup(this.grupoSeleccionado.role) + ); + } + + openDestroyGroupModal() { + if (!this.grupoSeleccionado) { + this.showNotification("warning", "i18n.warning.group.noSelected"); + return; + } + + if (!roleCanDestroyGroup(this.grupoSeleccionado.role)) { + this.showNotification("error", "i18n.warning.group.noPermission"); + return; + } + + this.createModalRef = this.modalService.open(DestroyGroupModal); + + this.createModalRef.componentInstance.groupName = + this.grupoSeleccionado.name; + this.createModalRef.componentInstance.groupId = this.grupoSeleccionado.id; + this.createModalRef.componentInstance.confirmGroupDestroyed = + this.confirmGroupDestroyed; + + // this.translateService + // .get("i18n.warning.group.destroy", { + // group: this.grupoSeleccionado.name, + // }) + // .subscribe((res) => { + // if (confirm(res)) { + // this.groupService.destroyGroup(this.grupoSeleccionado.id).subscribe( + // (data) => { + // this.loadGroups(); + // this.grupoSeleccionado = undefined; + // }, + // (error) => { + // this.notifService.error(error); + // } + // ); + } + + confirmGroupDestroyed() { + this.desseleccionarGrupo(); + this.loadGroups(); + } + mostrarModalCalificarEntrega() { // Mostrar el modal this.modalQualifyDelivery = true; diff --git a/Frontend Angular 4/src/app/shared/components/action-confirmation-modal/action-confirmation-modal.component.html b/Frontend Angular 4/src/app/shared/components/action-confirmation-modal/action-confirmation-modal.component.html new file mode 100644 index 0000000..f80e61c --- /dev/null +++ b/Frontend Angular 4/src/app/shared/components/action-confirmation-modal/action-confirmation-modal.component.html @@ -0,0 +1,27 @@ +<div class="modal-header"> + <span class="modal-title font-bold" id="modal-title">{{ title }}</span> + <button + aria-label="Cerrar diálogo" + (click)="cancelAction()" + part="close-button" + class="close-button fa fa-close" + type="button" + ></button> +</div> +<div class="modal-body"> + <div class="mb-3"> + {{ message }} + </div> + <div class="flex justify-end"> + <button + (click)="cancelAction()" + class="btn btn-secondary mr-3" + slot="secondary" + > + Cancelar + </button> + <button (click)="confirmAction()" class="btn btn-primary" slot="primary"> + Confirmar + </button> + </div> +</div> diff --git a/Frontend Angular 4/src/app/shared/components/action-confirmation-modal/action-confirmation-modal.component.ts b/Frontend Angular 4/src/app/shared/components/action-confirmation-modal/action-confirmation-modal.component.ts new file mode 100644 index 0000000..47b26b3 --- /dev/null +++ b/Frontend Angular 4/src/app/shared/components/action-confirmation-modal/action-confirmation-modal.component.ts @@ -0,0 +1,15 @@ +import { Component, ChangeDetectionStrategy, Input } from "@angular/core"; + +import { NgbActiveModal } from "@ng-bootstrap/ng-bootstrap"; + +@Component({ + selector: "app-action-confirmation-modal", + templateUrl: "./action-confirmation-modal.component.html", + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ActionConfirmationModal { + @Input() title: string; + @Input() message: string; + @Input() confirmAction: () => void; + @Input() cancelAction: () => void; +} diff --git a/Frontend Angular 4/src/app/shared/components/destroy-group-modal/destroy-group-modal.component.html b/Frontend Angular 4/src/app/shared/components/destroy-group-modal/destroy-group-modal.component.html new file mode 100644 index 0000000..0451b0c --- /dev/null +++ b/Frontend Angular 4/src/app/shared/components/destroy-group-modal/destroy-group-modal.component.html @@ -0,0 +1,6 @@ +<app-action-confirmation-modal + [title]="title" + [message]="message" + [confirmAction]="destroyGroup" + [cancelAction]="cancel" +></app-action-confirmation-modal> diff --git a/Frontend Angular 4/src/app/shared/components/destroy-group-modal/destroy-group-modal.component.ts b/Frontend Angular 4/src/app/shared/components/destroy-group-modal/destroy-group-modal.component.ts new file mode 100644 index 0000000..62e1962 --- /dev/null +++ b/Frontend Angular 4/src/app/shared/components/destroy-group-modal/destroy-group-modal.component.ts @@ -0,0 +1,53 @@ +import { + Component, + ChangeDetectionStrategy, + Input, + OnInit, +} from "@angular/core"; + +import { NgbActiveModal } from "@ng-bootstrap/ng-bootstrap"; + +import { GroupService } from "../../services/group.service"; + +@Component({ + selector: "app-destroy-group-modal", + templateUrl: "./destroy-group-modal.component.html", + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class DestroyGroupModal implements OnInit { + @Input() groupName: string; + @Input() groupId: number; + /** + * Se dispara cuando se quiere administrar los usuarios del grupo + */ + @Input() confirmGroupDestroyed: () => void; + title = "Destruir Grupo"; + message: string; + + constructor( + public modal: NgbActiveModal, + private groupService: GroupService + ) {} + + ngOnInit(): void { + this.groupName = `${this.groupName}`; + + this.message = `¿Estás seguro de que deseas destruir el grupo ${this.groupName}?`; + this.destroyGroup = this.destroyGroup.bind(this); + this.cancel = this.cancel.bind(this); + } + + destroyGroup(): void { + this.groupService.destroyGroup(this.groupId).subscribe({ + next: (_) => { + this.confirmGroupDestroyed(); + this.modal.close(); + }, + error: (error) => console.log(error), + }); + } + + cancel(): void { + this.modal.dismiss(); + } +} diff --git a/Frontend Angular 4/src/app/shared/components/index.ts b/Frontend Angular 4/src/app/shared/components/index.ts index 0612484..d7aadd0 100755 --- a/Frontend Angular 4/src/app/shared/components/index.ts +++ b/Frontend Angular 4/src/app/shared/components/index.ts @@ -10,3 +10,5 @@ export * from "./select/select.component"; export * from "./add-users-modal/add-users-modal.component"; export * from "./create-group-modal/create-group-modal.component"; export * from "./manage-group-users-modal/manage-group-users-modal.component"; +export * from "./action-confirmation-modal/action-confirmation-modal.component"; +export * from "./destroy-group-modal/destroy-group-modal.component"; diff --git a/Frontend Angular 4/src/app/shared/objects/grupo.ts b/Frontend Angular 4/src/app/shared/objects/grupo.ts index 157e4e4..832f220 100755 --- a/Frontend Angular 4/src/app/shared/objects/grupo.ts +++ b/Frontend Angular 4/src/app/shared/objects/grupo.ts @@ -6,4 +6,5 @@ export class Group { name: string; files?: MFile[]; users?: User[]; + role?: string; } diff --git a/Frontend Angular 4/src/app/shared/services/group.service.ts b/Frontend Angular 4/src/app/shared/services/group.service.ts index bc32858..f1335f8 100644 --- a/Frontend Angular 4/src/app/shared/services/group.service.ts +++ b/Frontend Angular 4/src/app/shared/services/group.service.ts @@ -10,7 +10,6 @@ import { import { Archivo, Evaluacion, MFile } from "../objects/archivo-types"; import { Group } from "../objects/grupo"; -import { SERVER } from "../config"; import { TranslateService } from "@ngx-translate/core"; import { AuthenticationService } from "./authentication.service"; import { catchError } from "rxjs/operators"; @@ -198,12 +197,12 @@ export class GroupService { ); } - // deleteFile(file_id): Observable<HttpResponse<Object>> { - // return this.http.delete<Object>(`${DELETE_FILE}/${file_id}`, { - // observe: "response", - // headers: this.postHeaders(), - // }); - // } + destroyGroup(group_id): Observable<HttpResponse<Object>> { + return this.http.delete<Object>(`${DELETE_GROUP}/${group_id}`, { + observe: "response", + headers: this.postHeaders(), + }); + } // destroyAuxFile(): void { // this.http.delete<Object>(DESTROY_AUX_DOCUMENT, { diff --git a/Frontend Angular 4/src/app/shared/shared.module.ts b/Frontend Angular 4/src/app/shared/shared.module.ts index 8fcf224..06214de 100644 --- a/Frontend Angular 4/src/app/shared/shared.module.ts +++ b/Frontend Angular 4/src/app/shared/shared.module.ts @@ -17,7 +17,8 @@ import { MatChipsModule } from "@angular/material/chips"; import { MatIconModule } from "@angular/material/icon"; import { NgbDropdownModule } from "@ng-bootstrap/ng-bootstrap"; import { AddUsersModal } from "./components"; - +import { ActionConfirmationModal } from "./components"; +import { DestroyGroupModal } from "./components"; @NgModule({ imports: [ TitleCaseModule, @@ -40,6 +41,8 @@ import { AddUsersModal } from "./components"; ChipInputComponent, SelectComponent, AddUsersModal, + ActionConfirmationModal, + DestroyGroupModal, ], exports: [MainButtonComponent, CreateFileModal], }) diff --git a/Frontend Angular 4/src/app/utils.ts b/Frontend Angular 4/src/app/utils.ts index 35a774c..f66570f 100644 --- a/Frontend Angular 4/src/app/utils.ts +++ b/Frontend Angular 4/src/app/utils.ts @@ -41,3 +41,11 @@ export function findInTree<T extends MFile | Archivo>( export function roleCanShareFile(role: string): boolean { return role === "owner" || role === "manager"; } + +export function roleCanManageGroupUsers(role: string): boolean { + return role === "owner" || role === "manager"; +} + +export function roleCanDestroyGroup(role: string): boolean { + return role === "owner"; +} diff --git a/Frontend Angular 4/src/assets/i18n/en.json b/Frontend Angular 4/src/assets/i18n/en.json index af85514..246f220 100755 --- a/Frontend Angular 4/src/assets/i18n/en.json +++ b/Frontend Angular 4/src/assets/i18n/en.json @@ -135,7 +135,8 @@ "error": "An error occurred while sending the email" }, "group": { - "manageGroupUsers": "Manage group users" + "manageGroupUsers": "Manage group users", + "destroyGroupTooltip": "Destroy Group" }, "updatePassword": { "success": "Your password has been updated, you will be redirected shortly", diff --git a/Frontend Angular 4/src/assets/i18n/es.json b/Frontend Angular 4/src/assets/i18n/es.json index 2d195ce..44ac256 100755 --- a/Frontend Angular 4/src/assets/i18n/es.json +++ b/Frontend Angular 4/src/assets/i18n/es.json @@ -135,7 +135,8 @@ "error": "Error al enviar el email" }, "group": { - "manageGroupUsers": "Administrar usuarios del grupo" + "manageGroupUsers": "Administrar usuarios del grupo", + "destroyGroupTooltip": "Destruir Grupo" }, "updatePassword": { "success": "Su contraseña ha sido actualizada, será redirigido en 5 segundos", -- GitLab From 3e153be4b731e0056e0ccac892133c69b26b2d88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rodr=C3=ADguez?= <diego.rodriguez@rootstrap.com> Date: Wed, 22 Jan 2025 15:52:17 -0300 Subject: [PATCH 3/9] Feedback wip: can request and return feedback --- .../app/layout/grupos/grupos.component.html | 141 +++++- .../src/app/layout/grupos/grupos.component.ts | 449 +++++++++++++++++- .../src/app/layout/layout-routing.module.ts | 5 + .../src/app/layout/layout.module.ts | 4 + .../app/layout/matefun/matefun.component.html | 26 + .../app/layout/matefun/matefun.component.ts | 272 +++++++++-- .../destroy-group-modal.component.ts | 11 +- .../src/app/shared/components/index.ts | 2 + .../request-feedback-modal.component.html | 6 + .../request-feedback-modal.component.ts | 65 +++ ...t-group-file-feedback-modal.component.html | 6 + ...est-group-file-feedback-modal.component.ts | 54 +++ .../return-feedback-modal.component.html | 6 + .../return-feedback-modal.component.ts | 68 +++ Frontend Angular 4/src/app/shared/config.ts | 16 + .../src/app/shared/objects/archivo-types.ts | 4 + .../app/shared/services/document.service.ts | 11 +- .../src/app/shared/services/file.service.ts | 4 + .../shared/services/group-document.service.ts | 311 ++++++++++++ .../app/shared/services/group-file.service.ts | 264 ++++++++++ .../src/app/shared/services/group.service.ts | 44 ++ .../src/app/shared/shared.module.ts | 28 +- Frontend Angular 4/src/assets/i18n/en.json | 4 +- Frontend Angular 4/src/assets/i18n/es.json | 4 +- Frontend Angular 4/src/styles/console.css | 2 +- 25 files changed, 1712 insertions(+), 95 deletions(-) create mode 100644 Frontend Angular 4/src/app/shared/components/request-feedback-modal/request-feedback-modal.component.html create mode 100644 Frontend Angular 4/src/app/shared/components/request-feedback-modal/request-feedback-modal.component.ts create mode 100644 Frontend Angular 4/src/app/shared/components/request-group-file-feedback-modal/request-group-file-feedback-modal.component.html create mode 100644 Frontend Angular 4/src/app/shared/components/request-group-file-feedback-modal/request-group-file-feedback-modal.component.ts create mode 100644 Frontend Angular 4/src/app/shared/components/return-feedback-modal/return-feedback-modal.component.html create mode 100644 Frontend Angular 4/src/app/shared/components/return-feedback-modal/return-feedback-modal.component.ts create mode 100644 Frontend Angular 4/src/app/shared/services/group-document.service.ts create mode 100644 Frontend Angular 4/src/app/shared/services/group-file.service.ts diff --git a/Frontend Angular 4/src/app/layout/grupos/grupos.component.html b/Frontend Angular 4/src/app/layout/grupos/grupos.component.html index 9c374cb..74c0e81 100755 --- a/Frontend Angular 4/src/app/layout/grupos/grupos.component.html +++ b/Frontend Angular 4/src/app/layout/grupos/grupos.component.html @@ -60,7 +60,7 @@ [destroyOnHide]="false" > <li [ngbNavItem]="1"> - <a ngbNavLink>Alumnos</a> + <a ngbNavLink>Users</a> <ng-template ngbNavContent> <div class="card"> <div> @@ -90,7 +90,6 @@ <i class="fa fa-users"></i> </button> <button - *ngIf="roleCanDestroyGroup()" class="btn btn-sm btn-secondary pull-right" style="cursor: pointer; margin-top: -35px; margin-right: 71px" (click)="openDestroyGroupModal()" @@ -136,11 +135,11 @@ <a ngbNavLink>Archivos</a> <ng-template ngbNavContent> <div class="card"> - <div> + <div class="relative pull-right right-0"> <button class="btn btn-sm btn-secondary pull-right" style="cursor: pointer; margin-top: -35px; margin-right: 1px" - (click)="desseleccionarGrupo()" + (click)="navBack()" ngbPopover="{{ 'i18n.action.goBack' | translate | titleCase }}" @@ -149,9 +148,31 @@ > <i class="fa fa-arrow-up"></i> </button> + <button + ngbPopover="{{ 'i18n.action.new' | translate | titleCase }}" + triggers="mouseenter:mouseleave" + placement="bottom" + type="button" + data-toggle="dropdown" + class="btn btn-sm btn-secondary pull-right" + style="cursor: pointer; margin-top: -35px; margin-right: 36px" + aria-haspopup="true" + aria-expanded="false" + > + <i aria-hidden="true" class="fa fa-plus"></i> + </button> + <div class="dropdown-menu right-0" style="left: unset"> + <a class="dropdown-item" (click)="mkFile(true)"> + {{ "i18n.object.file" | translate | titleCase }} + </a> + <div role="separator" class="dropdown-divider"></div> + <a class="dropdown-item" (click)="mkFile(false)"> + {{ "i18n.object.folder" | translate | titleCase }} + </a> + </div> <p class="pull-right" - style="margin-top: -34px; margin-right: 60px" + style="margin-top: -34px; margin-right: 95px" > {{ grupoSeleccionado.name }} </p> @@ -162,12 +183,19 @@ style="min-height: 100px; overflow-y: scroll" > <div - *ngFor="let archivo of grupoSeleccionado.archivos" + *ngFor="let archivo of directorioActual.archivos" (click)="seleccionarArchivo(archivo)" class="col-sm-3 col-4 matefun-group-wrapper" > <i + *ngIf="archivo.directorio" + class="fa fa-folder matefun-fa-folder" + aria-hidden="true" + ></i> + <i + *ngIf="!archivo.directorio" class="fa fa-file-text matefun-fa-file" + [class.text-blue-400]="archivo.feedbackRequested" aria-hidden="true" ></i> <p>{{ archivo.nombre }}</p> @@ -182,14 +210,14 @@ <div [ngbNavOutlet]="nav" class="mt-2" *ngIf="grupoSeleccionado"></div> </div> <div class="col-lg-7"> - <div class="card" *ngIf="selectedUser"> + <div class="card" *ngIf="selectedUser && !archivoSeleccionado"> <div class="card-block"> <div class="row listadoEntregasAlumnoGrupos" style="min-height: 100px; overflow-y: scroll" > <div - *ngFor="let entrega of selectedUser.archivos" + *ngFor="let entrega of selectedUserDirectorioActual" (click)="seleccionarEntrega(entrega)" class="col-sm-3 col-4 matefun-file-wrapper" > @@ -201,7 +229,7 @@ <p>{{ entrega.nombre }}</p> </div> <div - *ngIf="selectedUser.archivos.length == 0" + *ngIf="selectedUser && selectedUserDirectorioActual.length == 0" style="width: 100%; text-align: center" > <i @@ -253,14 +281,59 @@ Calificar </button> <button - *ngIf="esArchivoGrupo()" - ngbPopover="Cargar/Editar" + ngbPopover="{{ 'i18n.action.load' | translate | titleCase }}/{{ + 'i18n.action.edit' | translate | titleCase + }}" + placement="bottom" + triggers="mouseenter:mouseleave" + class="btn btn-sm btn-secondary pull-left mr-2" + (click)="cargarArchivo()" + > + <i aria-hidden="true" class="fa fa-pencil"></i> + </button> + <button + ngbPopover="{{ 'i18n.action.delete' | translate | titleCase }}" placement="bottom" triggers="mouseenter:mouseleave" class="btn btn-sm btn-secondary pull-left mr-2" - (click)="cargarArchivoCompartido()" + (click)="mostrarEliminarDialogo()" > - <i class="fa fa-pencil"></i> + <i aria-hidden="true" class="fa fa-remove"></i> + </button> + <button + ngbPopover="{{ 'i18n.action.move' | translate | titleCase }} {{ + 'i18n.object.file' | translate | titleCase + }}" + placement="bottom" + triggers="mouseenter:mouseleave" + class="btn btn-sm btn-secondary pull-left mr-2" + (click)="seleccionarDirectorioAMover()" + > + <i aria-hidden="true" class="fa fa-cut"></i> + </button> + <button + *ngIf="canRequestFeedback()" + ngbPopover="{{ + 'i18n.action.requestFeedback' | translate | titleCase + }} {{ 'i18n.object.file' | translate | titleCase }}" + placement="bottom" + triggers="mouseenter:mouseleave" + class="btn btn-sm btn-secondary pull-left mr-2" + (click)="requestFeedback()" + > + <i aria-hidden="true" class="fa fa-exchange"></i> + </button> + <button + *ngIf="canReturnFeedback()" + ngbPopover="{{ + 'i18n.action.returnFeedback' | translate | titleCase + }} {{ 'i18n.object.file' | translate | titleCase }}" + placement="bottom" + triggers="mouseenter:mouseleave" + class="btn btn-sm btn-secondary pull-left mr-2" + (click)="returnFeedback()" + > + <i aria-hidden="true" class="fa fa-exchange"></i> </button> <div class="pull-left"> Nombre: {{ archivoSeleccionado?.nombre }} - Creado: @@ -296,3 +369,45 @@ *ngIf="modalQualifyDelivery" > </matefun-modal-calificar-entrega> + +<matefun-modal-nuevo-archivo + confirmLabel="{{ 'i18n.action.create' | translate | titleCase }}" + fileDescriptionLabel="{{ 'i18n.object.descr' | translate | titleCase }}:" + fileNameLabel="{{ 'i18n.object.name' | translate | titleCase }}:" + header="{{ 'i18n.action.new' | translate | titleCase }} + {{ + (modalTypeIsFile ? 'i18n.object.file' : 'i18n.object.folder') + | translate + | titleCase + }} " + [opened]="modalCreateFileOpened" + [typeOfFile]="modalTypeIsFile ? 'file' : 'directory'" + (close)="modalCreateFile = false" + (confirmFileCreation)=" + modalTypeIsFile + ? confirmDocumentCreation($event) + : confirmFileCreation($event) + " + *ngIf="modalCreateFile" +> +</matefun-modal-nuevo-archivo> + +<matefun-modal-seleccionar-directorio + confirmLabel="{{ 'i18n.action.move' | translate | titleCase }} {{ + 'i18n.object.here' | translate + }}" + [currentDirectory]="currentDirOfFileToMove" + [fileIdToMove]="archivoSeleccionado ? archivoSeleccionado.id : -1" + fileNameLabel="{{ 'i18n.object.name' | translate | titleCase }}" + header="{{ 'i18n.msg.file.move' | translate }}" + [initialPath]="currentPath" + navigateBackLabel="{{ 'i18n.action.goBack' | translate | titleCase }}" + [opened]="modalMoveFileOpened" + type-of-modal="move" + (close)="modalMoveFile = false" + (confirmFileCreation)="confirmFileMove($event)" + (navBack)="navigateBackModal($event)" + (navTo)="currentDirOfFileToMove = $event.detail" + *ngIf="modalMoveFile" +> +</matefun-modal-seleccionar-directorio> diff --git a/Frontend Angular 4/src/app/layout/grupos/grupos.component.ts b/Frontend Angular 4/src/app/layout/grupos/grupos.component.ts index d480280..02c939e 100755 --- a/Frontend Angular 4/src/app/layout/grupos/grupos.component.ts +++ b/Frontend Angular 4/src/app/layout/grupos/grupos.component.ts @@ -1,7 +1,11 @@ import { ChangeDetectorRef, Component } from "@angular/core"; import { Router } from "@angular/router"; -import { Archivo, Evaluacion } from "../../shared/objects/archivo-types"; +import { + Archivo, + Evaluacion, + MDocument, +} from "../../shared/objects/archivo-types"; import { Group } from "../../shared/objects/grupo"; import { Usuario } from "../../shared/objects/usuario"; import { AuthenticationService } from "../../shared/services/authentication.service"; @@ -11,11 +15,27 @@ import { NgbPopoverConfig, NgbPopover } from "@ng-bootstrap/ng-bootstrap"; import { NotificacionService } from "../../shared/services/notificacion.service"; import { TranslateService } from "@ngx-translate/core"; import { GroupService } from "../../shared/services/group.service"; -import { roleCanManageGroupUsers, roleCanDestroyGroup } from "app/utils"; +import { GroupFileService } from "../../shared/services/group-file.service"; +import { FileService } from "../../shared/services/file.service"; +import { DocumentService } from "../../shared/services/document.service"; +import { GroupDocumentService } from "../../shared/services/group-document.service"; +import { + roleCanManageGroupUsers, + roleCanDestroyGroup, + findInTree, +} from "app/utils"; import { NgbModal } from "@ng-bootstrap/ng-bootstrap"; import { CreateGroupModal } from "../../shared/components/create-group-modal/create-group-modal.component"; import { ManageGroupUsersModal } from "app/shared/components/manage-group-users-modal/manage-group-users-modal.component"; -import { DestroyGroupModal } from "app/shared"; +import { + DestroyGroupModal, + RequestFeedbackModal, + ReturnFeedbackModal, +} from "app/shared"; + +const STARTS_WITH_CAPITAL_LETTER_REGEX = /^[A-Z]/; + +const ID_ROOT_DIR = -1; @Component({ selector: "grupos", @@ -23,12 +43,14 @@ import { DestroyGroupModal } from "app/shared"; }) export class GruposComponent { archivos: Archivo[] = []; + selectedUserArchivos: Archivo[] = []; grupos: Group[] = []; grupoSeleccionado: Group = undefined; selectedUser: Usuario = undefined; archivoSeleccionado: Archivo = undefined; + selectedFile: Archivo = undefined; tipoArchivo: string = undefined; @@ -39,8 +61,12 @@ export class GruposComponent { configCodeMirror = JSON.parse(localStorage.getItem("codeMirrorConfig")); translateService: any; + selectedUserFileArray: any; + selectedUserDirectorioActual: any; + createModalRef: any; + preview: string = ""; // - - - - - - - - - - - - Modal show confirm - - - - - - - - - - - - /** * Con `true` se renderiza el modal de calificar entrega. @@ -68,6 +94,51 @@ export class GruposComponent { }, ]; + // - - - - - - - - - - - - - Modal create file - - - - - - - - - - - - - + /** + * Con `true` se renderiza el modal de crear un archivo + */ + modalCreateFile = false; + + /** + * Con `true` se configura el modal para que se muestre la interfaz de + * agregar/borrar archivo. De otro modo, se muestra la interfaz de + * agregar/borrar directorio + */ + modalTypeIsFile = true; + + /** + * Con `true` se indica que el modal -de crear un archivo- se quiere abrir. + * Útil para avisar al modal que anime el dismiss antes de que se elimine del + * DOM + */ + modalCreateFileOpened = true; + + /** + * Directorio actual sobre el cual será desplegada la lista de directorios + * del modal mover archivo. + */ + currentDirOfFileToMove: Archivo; + + /** + * Determina la ruta en donde se encuentra ubicado el archivo/directorio + * seleccionado actualmente. + */ + currentPath: string = "/"; + + // - - - - - - - - - - - - - Modal move file - - - - - - - - - - - - - + /** + * Con `true` se renderiza el modal de mover un archivo + */ + modalMoveFile = false; + + /** + * Con `true` se indica que el modal -de mover un archivo- se quiere abrir. + * Útil para avisar al modal que anime el dismiss antes de que se elimine del + * DOM + */ + modalMoveFileOpened = true; + constructor( private router: Router, private authService: AuthenticationService, @@ -76,6 +147,10 @@ export class GruposComponent { private sessionService: SessionService, public translate: TranslateService, public groupService: GroupService, + public groupFileService: GroupFileService, + public documentService: DocumentService, + public groupDocumentService: GroupDocumentService, + public fileService: FileService, private modalService: NgbModal, private changeDetectorRef: ChangeDetectorRef ) { @@ -144,9 +219,12 @@ export class GruposComponent { } ordenarArchivos() { - // this.grupoSeleccionado.archivos = this.grupoSeleccionado.archivos.sort( - // this.ordenarAlph - // ); + // var tipo = this.sortFunction; + // if (tipo === "tipo") { + // this.ordenarMixto(); + // } else if (tipo === "fecha") { + // this.ordenarFechaCreacion(); + // } } //ordeno los archivos del alumno (los archivos entregados.) @@ -183,37 +261,222 @@ export class GruposComponent { // ); } + /** + * Renderiza en pantalla el modal de agregar archivos o directorios, + * dependiendo del valor de `modalTypeIsFile`. + * @param modalTypeIsFile Con `true` se indica que el modal a renderizar es el de agregar archivo. De otro modo, se renderiza el de agregar directorio. + */ + mkFile(modalTypeIsFile: boolean) { + this.modalTypeIsFile = modalTypeIsFile; + + // Mostrar el modal + this.modalCreateFile = true; + this.modalCreateFileOpened = true; + } + seleccionarGrupo(grupo) { this.grupoSeleccionado = grupo; this.ordenarAlumnos(); - this.ordenarArchivos(); - this.archivoSeleccionado = undefined; + this.loadFilesAndFolders(grupo.id); this.selectedUser = undefined; } + loadFilesAndFolders(grupoId: number, directorioActualId = null) { + this.groupFileService.getGroupFiles(grupoId).subscribe( + (files) => { + this.tree = this.fileService.fileToArchivo(files["files"]); + + this.archivos = [this.tree]; + this.loading = false; + + if (directorioActualId) { + this.directorioActual = findInTree(this.archivos, directorioActualId); + } else { + this.directorioActual = this.tree; + } + this.ordenarArchivos(); + this.sessionService.setArchivosTree(this.tree); + + this.currentDirOfFileToMove = this.directorioActual; + }, + (error) => console.log(error) + ); + } + desseleccionarGrupo() { this.grupoSeleccionado = undefined; this.archivoSeleccionado = undefined; this.selectedUser = undefined; } - seleccionarUser(alumno) { - if (!(typeof alumno === "undefined")) { - this.selectedUser = alumno; - this.ordenarArchivosAlumno(); - this.archivoSeleccionado = undefined; + seleccionarUser(user) { + this.groupFileService + .getFeedbackRequestedGroupFiles(this.grupoSeleccionado.id, user.id) + .subscribe( + (files) => { + this.selectedUserFileArray = files["files"].map((file) => + this.fileService.fileToArchivo(file) + ); + this.selectedUserArchivos = [this.selectedUserFileArray]; + this.loading = false; + + this.selectedUserDirectorioActual = this.selectedUserFileArray; + this.ordenarArchivos(); + this.sessionService.setArchivosTree(this.selectedUserFileArray); + + this.selectedUser = user; + + this.archivoSeleccionado = undefined; + this.selectedFile = undefined; // Probando + }, + (error) => console.log(error) + ); + } + + canRequestFeedback() { + return ( + !!this.grupoSeleccionado.id && + !!this.selectedFile && + this.selectedFile.feedbackRequested !== undefined && + !this.selectedFile.feedbackRequested + ); + } + + requestFeedback() { + this.openRequestFeedbackModal(); + } + + canReturnFeedback() { + return ( + !!this.grupoSeleccionado && + !!this.selectedFile && + this.selectedFile.feedbackRequested !== undefined && + this.selectedFile.feedbackRequested && + this.groupService.userCanReturnFeedback(this.grupoSeleccionado.role) + ); + } + + returnFeedback() { + this.openReturnFeedbackModal(); + } + + openReturnFeedbackModal() { + if (!this.grupoSeleccionado) { + this.showNotification("warning", "i18n.warning.group.noSelected"); + return; + } + + if (!this.selectedFile.fileId) { + this.showNotification("warning", "i18n.warning.file.noSelected"); + return; } + + this.createModalRef = this.modalService.open(ReturnFeedbackModal); + + this.createModalRef.componentInstance.groupName = null; + this.createModalRef.componentInstance.groupId = this.grupoSeleccionado; + this.createModalRef.componentInstance.fileName = this.selectedFile.nombre; + this.createModalRef.componentInstance.fileId = this.selectedFile.fileId; + this.createModalRef.componentInstance.userId = this.selectedUser.id; + this.createModalRef.componentInstance.confirmFeedbackReturned = + this.confirmFeedbackReturned; } + confirmFeedbackReturned() {} + seleccionarArchivo(archivo) { + this.selectedFile = archivo; + if (archivo.directorio) { + this.directorioActual = archivo; + // this.loadFilesAndFolders(this.grupoSeleccionado.id, archivo.id); + this.archivoSeleccionado = undefined; + this.selectedFile = undefined; // Probando + // this.selectedUser = undefined; + this.currentPath += `${archivo.nombre}/`; + this.preview = ""; + } else { + this.selectedUser = undefined; + this.tipoArchivo = "compartido"; + this.groupDocumentService + .getGroupDocument( + this.grupoSeleccionado.id, + archivo.documentId, + this.selectedUser?.id + ) + .subscribe( + (data) => { + const document = data["document"]; + const archivoDocument = + this.documentService.documentToArchivo(document); + this.actualizarArchivoSeleccionado(archivoDocument); + }, + (error) => console.log(error) + ); + } + } + + /** + * Actualiza el estado del archivo seleccionado, así como la preview del + * contenido de dicho archivo. + * @param archivo Archivo a seleccionar + */ + actualizarArchivoSeleccionado(archivo: Archivo) { this.archivoSeleccionado = archivo; - this.selectedUser = undefined; - this.tipoArchivo = "compartido"; + this.selectedFile = archivo; //Probando + this.preview = !!archivo?.contenido ? archivo.contenido : ""; + } + + /** + * Navega hacia el directorio padre en la vista principal de directorios. + * @param shouldUpdateSelectedFile Determina si se debe actualizar el archivo seleccionado cuando se navega hacia atrás + */ + navBack(shouldUpdateSelectedFile = true) { + const { padreId } = this.directorioActual; + + if (padreId === ID_ROOT_DIR) { + return; + } + + // Actualiza el current path + const lastDirectoryIndex = this.currentPath.lastIndexOf( + `${this.directorioActual.nombre}` + ); + + this.currentPath = this.currentPath.substring(0, lastDirectoryIndex); + + const padre = findInTree(this.archivos, padreId); + + if (shouldUpdateSelectedFile) { + // Cuando se selecciona un directorio cuyo id es el root, significa que ese + // es el último archivo de la rama de directorios. En otras palabras, el + // root se identifica porque su padreId es ID_ROOT_DIR. + const archivoSeleccionado = + padre.padreId == ID_ROOT_DIR ? undefined : padre; + this.actualizarArchivoSeleccionado(archivoSeleccionado); + } + + // Actualiza la vista de directorios y archivos + this.directorioActual = padre; } seleccionarEntrega(entrega) { - this.archivoSeleccionado = entrega; - this.selectedUser = undefined; + this.groupDocumentService + .getGroupDocument( + this.grupoSeleccionado.id, + entrega.documentId, + this.selectedUser.id + ) + .subscribe( + (data) => { + // this.archivoSeleccionado = entrega; + const document = data["document"]; + const archivoDocument = + this.documentService.documentToArchivo(document); + this.actualizarArchivoSeleccionado(archivoDocument); + }, + (error) => console.log(error) + ); + // this.selectedUser = undefined; this.tipoArchivo = "entrega"; } @@ -231,14 +494,35 @@ export class GruposComponent { ); } - openDestroyGroupModal() { - if (!this.grupoSeleccionado) { + openRequestFeedbackModal() { + if (!this.grupoSeleccionado.id) { this.showNotification("warning", "i18n.warning.group.noSelected"); return; } - if (!roleCanDestroyGroup(this.grupoSeleccionado.role)) { - this.showNotification("error", "i18n.warning.group.noPermission"); + if (!this.selectedFile.id) { + this.showNotification("warning", "i18n.warning.file.noSelected"); + return; + } + + this.createModalRef = this.modalService.open(RequestFeedbackModal); + + this.createModalRef.componentInstance.groupName = + this.grupoSeleccionado.name; + this.createModalRef.componentInstance.groupId = this.grupoSeleccionado.id; + this.createModalRef.componentInstance.fileName = + this.archivoSeleccionado.nombre; + this.createModalRef.componentInstance.fileId = + this.archivoSeleccionado.fileId; + this.createModalRef.componentInstance.confirmFeedbackRequested = + this.confirmFeedbackRequsted; + } + + confirmFeedbackRequsted() {} + + openDestroyGroupModal() { + if (!this.grupoSeleccionado) { + this.showNotification("warning", "i18n.warning.group.noSelected"); return; } @@ -249,6 +533,8 @@ export class GruposComponent { this.createModalRef.componentInstance.groupId = this.grupoSeleccionado.id; this.createModalRef.componentInstance.confirmGroupDestroyed = this.confirmGroupDestroyed; + this.createModalRef.componentInstance.userRole = + this.grupoSeleccionado.role; // this.translateService // .get("i18n.warning.group.destroy", { @@ -382,14 +668,131 @@ export class GruposComponent { // } else { // return false; // } + return true; } - cargarArchivoCompartido() { + cargarArchivo() { if (!this.archivoSeleccionado || this.archivoSeleccionado.directorio) { this.showNotification("warning", "i18n.warning.file.noSelected"); return; } this.sessionService.setArchivo(this.archivoSeleccionado); - this.router.navigate(["/matefun"]); + const userId = !!this.selectedUser + ? this.selectedUser.id + : JSON.parse(localStorage.getItem("currentUser"))["id"]; + + this.router.navigate([ + `/grupos/${this.grupoSeleccionado.id}/users/${userId}/matefun/${this.archivoSeleccionado.id}`, + ]); + } + + seleccionarDirectorioAMover() { + if (!this.archivoSeleccionado) { + this.showNotification("warning", "i18n.warning.file.noSelected"); + return; + } + + // Si el archivo es del alumno, se puede mover. + // No se controla por creador, dado que los compartidos mantienen este atributo + // if (!this.archivos.some((arch) => arch.id == this.archivoSeleccionado.id)) { + // this.showNotification("warning", "i18n.warning.file.noPermissionMove"); + // return; + // } + + // this.navBack(false); + if (this.archivoSeleccionado.directorio) { + this.currentDirOfFileToMove = this.directorioActual.parent; + } else { + this.currentDirOfFileToMove = this.directorioActual; + } + + // Mostrar el modal + this.modalMoveFile = true; + this.modalMoveFileOpened = true; + } + + /** + * Valida y confirma la creación del archivo. + * @param event Evento devuelto por el modal asociado para crear el archivo. En el detalle contiene el `nombre` y `descripcion` del archivo que se desea agregar. + */ + confirmFileCreation(event: CustomEvent) { + const { nombre, descripcion } = event.detail; + + // Antes que nada, se chequea que empiece con mayúscula + if (!STARTS_WITH_CAPITAL_LETTER_REGEX.test(nombre)) { + this.showNotification("warning", "i18n.warning.file.capitalLetter"); + return; + } + + const archivo = new Archivo(); + // archivo.cedulaCreador = this.directorioActual.cedulaCreador; // cedula + archivo.contenido = this.modalTypeIsFile ? "" : descripcion || ""; + archivo.directorio = !this.modalTypeIsFile; + archivo.editable = true; + archivo.fechaCreacion = new Date(); + archivo.nombre = nombre; + archivo.padreId = this.directorioActual.id; + archivo.archivos = []; + + const that = this; + const idDirectorioActual = this.directorioActual.id; + + const file = this.fileService.archivoToFile(archivo); + this.groupFileService + .createGroupFile(this.grupoSeleccionado.id, file) + .subscribe( + () => { + that.loadFilesAndFolders(idDirectorioActual); + + // Cerrar el modal en caso de éxito + that.modalCreateFileOpened = false; + }, + (error) => { + that.notifService.error(error.text()); + + // Cerrar el modal en caso de error + that.modalCreateFileOpened = false; + } + ); + } + + /** + * Valida y confirma la creación del archivo. + * @param event Evento devuelto por el modal asociado para crear el archivo. En el detalle contiene el `nombre` y `descripcion` del archivo que se desea agregar. + */ + confirmDocumentCreation(event: CustomEvent) { + const { nombre } = event.detail; + + // Antes que nada, se chequea que empiece con mayúscula + if (!STARTS_WITH_CAPITAL_LETTER_REGEX.test(nombre)) { + this.showNotification("warning", "i18n.warning.file.capitalLetter"); + return; + } + + const document = new MDocument(); + document.text_doc = ""; + document.title = nombre; + document.parent_id = this.directorioActual.id; + + const that = this; + const idDirectorioActual = this.directorioActual.id; + + this.documentService + .createDocument(document, this.grupoSeleccionado.id) + .subscribe( + () => { + that.loadFilesAndFolders( + this.grupoSeleccionado.id, + idDirectorioActual + ); + + that.modalCreateFileOpened = false; + }, + (error) => { + that.notifService.error(error.text()); + + that.modalCreateFileOpened = false; + } + ); } } diff --git a/Frontend Angular 4/src/app/layout/layout-routing.module.ts b/Frontend Angular 4/src/app/layout/layout-routing.module.ts index 0e510bb..9a4cbb8 100755 --- a/Frontend Angular 4/src/app/layout/layout-routing.module.ts +++ b/Frontend Angular 4/src/app/layout/layout-routing.module.ts @@ -17,6 +17,11 @@ const routes: Routes = [ loadChildren: () => import("./matefun/matefun.module").then((m) => m.MateFunModule), }, + { + path: "grupos/:groupId/users/:groupUserId/matefun/:id", + loadChildren: () => + import("./matefun/matefun.module").then((m) => m.MateFunModule), + }, { path: "archivos", loadChildren: () => diff --git a/Frontend Angular 4/src/app/layout/layout.module.ts b/Frontend Angular 4/src/app/layout/layout.module.ts index 3e4f59a..ca4ed8e 100755 --- a/Frontend Angular 4/src/app/layout/layout.module.ts +++ b/Frontend Angular 4/src/app/layout/layout.module.ts @@ -10,6 +10,8 @@ import { AuthenticationService } from "../shared/services/authentication.service import { HaskellService } from "../shared/services/haskell.service"; import { DocumentService } from "../shared/services/document.service"; import { FileService } from "../shared/services/file.service"; +import { GroupDocumentService } from "../shared/services/group-document.service"; +import { GroupFileService } from "../shared/services/group-file.service"; import { GroupService } from "../shared/services/group.service"; import { LtCodemirrorModule } from "lt-codemirror"; import { CodemirrorModule } from "@ctrl/ngx-codemirror"; @@ -35,6 +37,8 @@ import { TitleCaseModule } from "../shared/modules/titlecase.module"; HaskellService, DocumentService, FileService, + GroupDocumentService, + GroupFileService, GroupService, ], }) diff --git a/Frontend Angular 4/src/app/layout/matefun/matefun.component.html b/Frontend Angular 4/src/app/layout/matefun/matefun.component.html index 15d5a3e..7dbe99c 100755 --- a/Frontend Angular 4/src/app/layout/matefun/matefun.component.html +++ b/Frontend Angular 4/src/app/layout/matefun/matefun.component.html @@ -169,6 +169,32 @@ > <i class="fa fa-plus"></i> </button> + <button + *ngIf="canRequestFeedback()" + style="float: right" + (click)="requestFeedback()" + class="btn btn-sm btn-secondary" + ngbPopover="{{ + 'i18n.action.requestFeedback' | translate | titleCase + }} {{ 'i18n.object.file' | translate | titleCase }}" + triggers="mouseenter:mouseleave" + placement="bottom" + > + <i class="fa fa-exchange"></i> + </button> + <button + *ngIf="canReturnFeedback()" + style="float: right" + (click)="returnFeedback()" + class="btn btn-sm btn-secondary" + ngbPopover="{{ + 'i18n.action.returnFeedback' | translate | titleCase + }} {{ 'i18n.object.file' | translate | titleCase }}" + triggers="mouseenter:mouseleave" + placement="bottom" + > + <i class="fa fa-exchange"></i> + </button> <ng-template #popoverContent style="width: 15em"> <div style="width: 12em"> <div class="form-group"> diff --git a/Frontend Angular 4/src/app/layout/matefun/matefun.component.ts b/Frontend Angular 4/src/app/layout/matefun/matefun.component.ts index 5f65fc1..f8a3199 100755 --- a/Frontend Angular 4/src/app/layout/matefun/matefun.component.ts +++ b/Frontend Angular 4/src/app/layout/matefun/matefun.component.ts @@ -14,6 +14,7 @@ import { HaskellService } from "../../shared/services/haskell.service"; import { DocumentService } from "../../shared/services/document.service"; import { UserService } from "../../shared/services/user.service"; import { FileService } from "../../shared/services/file.service"; +import { GroupFileService } from "../../shared/services/group-file.service"; import { WebsocketService } from "../../shared/services/websocket.service"; import { ActionCableService } from "app/shared/services/actioncable.service"; import { UsuarioService } from "../../shared/services/usuario.service"; @@ -41,7 +42,12 @@ import { CodeMirrorBinding } from "y-codemirror"; import { NgbModal } from "@ng-bootstrap/ng-bootstrap"; import { CreateFileModal } from "../../shared/components/create-file-modal/create-file-modal.component"; import { ImportFileModal } from "../../shared/components/import-file-modal/import-file-modal.component"; -import { ShareFileModal } from "app/shared"; +import { + ShareFileModal, + RequestFeedbackModal, + ReturnFeedbackModal, +} from "app/shared"; +import { GroupDocumentService } from "../../shared/services/group-document.service"; import * as Y from "yjs"; @@ -67,7 +73,8 @@ import "./codemirror/addons/functions_definition_EN.js"; import "./codemirror/addons/functions_definition_ES.js"; import { Action } from "rxjs/internal/scheduler/Action"; import { findInTree, roleCanShareFile } from "app/utils"; -import { filter } from "rxjs"; +import { filter, Observable } from "rxjs"; +import { GroupService } from "app/shared/services/group.service"; var codeMirrorRef: any; var componentRef: any; @@ -80,6 +87,8 @@ var focus: any; providers: [WebsocketService, NgbPopoverConfig, UsuarioService], }) export class MateFunComponent implements OnInit, OnChanges { + currentUserGroupRole: string; + documentBelongsToCurrentUser: boolean; titlecasePipe: any; translateService: any; consoleDisable: boolean = false; @@ -180,6 +189,8 @@ export class MateFunComponent implements OnInit, OnChanges { activeTabId = 1; ydoc: null | Y.Doc = null; documentId: number | null = null; + groupId: number | null = null; + groupUserId: number | null = null; editorContainer: Element; editor: CodeMirror.Editor; yUndoManager: Y.UndoManager; @@ -224,6 +235,7 @@ export class MateFunComponent implements OnInit, OnChanges { private documentService: DocumentService, private userService: UserService, private fileService: FileService, + private groupFileService: GroupFileService, private authService: AuthenticationService, private ghciService: GHCIService, private notifService: NotificacionService, @@ -231,6 +243,8 @@ export class MateFunComponent implements OnInit, OnChanges { private usuarioService: UsuarioService, public translate: TranslateService, private actionCableService: ActionCableService, + public groupDocumentService: GroupDocumentService, + public groupService: GroupService, private router: Router, private route: ActivatedRoute, private modalService: NgbModal, @@ -399,10 +413,51 @@ export class MateFunComponent implements OnInit, OnChanges { getCurrentDocument(nextFunction: () => void = null) { this.documentId = parseInt(this.route.snapshot.paramMap.get("id")); - this.documentService.getDocument(this.documentId).subscribe({ + this.groupId = parseInt(this.route.snapshot.paramMap.get("groupId")); + this.groupUserId = parseInt( + this.route.snapshot.paramMap.get("groupUserId") + ); + let documentSubscription: Observable<{ + document: MDocument; + }> = null; + let fileSubscriptionService: { + getFile: (id: number) => Observable<{ + file: MFile; + }>; + } = null; + // TODO: Do something similar to this but for files, + // also the redirect to here when choosing a file in the groups page + // is setting the wrong user id + if (!!this.groupId && !!this.groupUserId) { + documentSubscription = this.groupDocumentService.getGroupDocument( + this.groupId, + this.documentId, + this.groupUserId + ); + + fileSubscriptionService = + this.groupFileService.getSpecificUserFileWithSetGroup( + this.groupId, + this.groupUserId + ); + } else { + documentSubscription = this.documentService.getDocument(this.documentId); + fileSubscriptionService = this.fileService; + } + documentSubscription.subscribe({ next: (data) => { this.archivo = this.documentService.documentToArchivo(data.document); - if (!!nextFunction) { + if (this.router.url.includes("grupos")) { + fileSubscriptionService + .getFile(this.archivo.fileId) + .subscribe((file) => { + this.archivo.feedbackRequested = file["file"].feedback_requested; + + if (!!nextFunction) { + nextFunction(); + } + }); + } else if (!!nextFunction) { nextFunction(); } }, @@ -412,6 +467,82 @@ export class MateFunComponent implements OnInit, OnChanges { }); } + canRequestFeedback() { + return ( + !!this.groupId && + !!this.archivo && + this.archivo.feedbackRequested !== undefined && + !this.archivo.feedbackRequested && + this.documentBelongsToCurrentUser + ); + } + + canReturnFeedback() { + return ( + !!this.groupId && + !!this.archivo && + this.archivo.feedbackRequested !== undefined && + this.archivo.feedbackRequested && + this.groupService.userCanReturnFeedback(this.currentUserGroupRole) + ); + // TODO: Do this and the other function + } + + requestFeedback() { + this.openRequestFeedbackModal(); + } + + returnFeedback() { + this.openReturnFeedbackModal(); + } + + openRequestFeedbackModal() { + if (!this.groupId) { + this.showNotification("warning", "i18n.warning.group.noSelected"); + return; + } + + if (!this.archivo.fileId) { + this.showNotification("warning", "i18n.warning.file.noSelected"); + return; + } + + this.createModalRef = this.modalService.open(RequestFeedbackModal); + + this.createModalRef.componentInstance.groupName = null; + this.createModalRef.componentInstance.groupId = this.groupId; + this.createModalRef.componentInstance.fileName = this.archivo.nombre; + this.createModalRef.componentInstance.fileId = this.archivo.fileId; + this.createModalRef.componentInstance.confirmFeedbackRequested = + this.confirmFeedbackRequsted; + } + + confirmFeedbackRequsted() {} + + openReturnFeedbackModal() { + if (!this.groupId) { + this.showNotification("warning", "i18n.warning.group.noSelected"); + return; + } + + if (!this.archivo.fileId) { + this.showNotification("warning", "i18n.warning.file.noSelected"); + return; + } + + this.createModalRef = this.modalService.open(ReturnFeedbackModal); + + this.createModalRef.componentInstance.groupName = null; + this.createModalRef.componentInstance.groupId = this.groupId; + this.createModalRef.componentInstance.fileName = this.archivo.nombre; + this.createModalRef.componentInstance.fileId = this.archivo.fileId; + this.createModalRef.componentInstance.userId = this.groupUserId; + this.createModalRef.componentInstance.confirmFeedbackReturned = + this.confirmFeedbackReturned; + } + + confirmFeedbackReturned() {} + ngOnChanges() { // const docId = this.route.snapshot.paramMap.get("id"); // this.confirmFileCreation = this.confirmFileCreation.bind(this); @@ -458,10 +589,34 @@ export class MateFunComponent implements OnInit, OnChanges { this.configCodeMirrorDefinicion["readOnly"] = true; // This was always on ngOnInit + + this.groupId = !!this.route.snapshot.paramMap.get("groupId") + ? parseInt(this.route.snapshot.paramMap.get("groupId")) + : null; + this.groupUserId = !!this.route.snapshot.paramMap.get("groupUserId") + ? parseInt(this.route.snapshot.paramMap.get("groupUserId")) + : null; + if (!!this.route.snapshot.paramMap.get("id")) { this.getCurrentDocument(() => { this.copiaContenidoArchivo = this.archivo.contenido; this.copiaNombreArchivo = this.archivo.nombre; + + if ( + this.router.url.includes("grupos") && + !!this.groupId && + !!this.groupUserId + ) { + this.groupFileService + .getSpecificUserGroupFiles(this.groupId, this.groupUserId) + .subscribe((data) => { + this.importFilesData(data); + }); + } else { + this.fileService.getFiles().subscribe((data) => { + this.importFilesData(data); + }); + } }); } else { this.newFile(); @@ -588,38 +743,42 @@ export class MateFunComponent implements OnInit, OnChanges { this.configCodeMirror.mode.name = "matefun-ES"; } - this.fileService.getFiles().subscribe((files) => { - let matefun_files = files["files"]; - matefun_files = this.fileService.completeFiles(matefun_files); - this.sessionService.setUserFiles(matefun_files); - let fileArchivos = this.fileService.fileToArchivo(matefun_files); - - this.archivosTree = fileArchivos; - if (!!this.editor) { - (this.editor as any).options.files = this.archivosTree; - } - this.sessionService.setArchivosTree(this.archivosTree); - }); + // this.fileService.getFiles().subscribe((files) => { + // let matefun_files = files["files"]; + // matefun_files = this.fileService.completeFiles(matefun_files); + // this.sessionService.setUserFiles(matefun_files); + // let fileArchivos = this.fileService.fileToArchivo(matefun_files); + + // this.archivosTree = fileArchivos; + // if (!!this.editor) { + // (this.editor as any).options.files = this.archivosTree; + // } + // this.sessionService.setArchivosTree(this.archivosTree); + // }); + } - this.fileService.getFiles().subscribe((data) => { - let matefun_files = data["files"]; - matefun_files = this.fileService.completeFiles(matefun_files); - const tree = this.fileService.fileToArchivo(matefun_files); - this.archivosTree = tree; - this.sessionService.setArchivosTree(tree); - const file = findInTree([tree], this.archivo.fileId); - const directorioActual = findInTree([tree], file.padreId); - this.sessionService.setDirectorioActual(directorioActual); - this.documentService - .getDocuments( - directorioActual.archivos.map((archivo) => { - return archivo.documentId; - }) - ) - .subscribe((data) => { - this.sessionService.setCurrentDirectoryDocuments(data["documents"]); - }); - }); + importFilesData(data) { + let matefun_files = data["files"]; + matefun_files = this.fileService.completeFiles(matefun_files); + this.sessionService.setUserFiles(matefun_files); + const tree = this.fileService.fileToArchivo(matefun_files); + this.archivosTree = tree; + if (!!this.editor) { + (this.editor as any).options.files = this.archivosTree; + } + this.sessionService.setArchivosTree(tree); + const file = findInTree([tree], this.archivo.fileId); + const directorioActual = findInTree([tree], file.padreId); + this.sessionService.setDirectorioActual(directorioActual); + this.documentService + .getDocuments( + directorioActual.archivos.map((archivo) => { + return archivo.documentId; + }) + ) + .subscribe((data) => { + this.sessionService.setCurrentDirectoryDocuments(data["documents"]); + }); } ngAfterViewInit() { @@ -767,15 +926,35 @@ export class MateFunComponent implements OnInit, OnChanges { if (!!this.archivo.users) { if (this.archivo.users.length > 0) { const id = JSON.parse(localStorage.getItem("currentUser"))["id"]; - readOnly = - this.archivo.users.find((user) => { - return user.id === id; - }).role === "viewer"; + const user = this.archivo.users.find((user) => { + return user.id === id; + }); + this.documentBelongsToCurrentUser = !!user; - this.editor.setOption("readOnly", readOnly); + if (!!user) { + readOnly = user.role === "viewer"; + } + if ((!user || readOnly == true) && !!this.groupId) { + const id = JSON.parse(localStorage.getItem("currentUser"))["id"]; + this.groupService.getUserRole(this.groupId, id).subscribe((data) => { + this.currentUserGroupRole = data.role.role; + + this.editor.setOption( + "readOnly", + this.groupService.userCanEditAnyGroupFile( + this.currentUserGroupRole + ) + ); + }); + } else { + this.editor.setOption("readOnly", readOnly); + } } else { + this.documentBelongsToCurrentUser = true; this.editor.setOption("readOnly", false); } + } else { + this.documentBelongsToCurrentUser = true; } if (!!this.binding) { @@ -802,11 +981,22 @@ export class MateFunComponent implements OnInit, OnChanges { this.ydoc = yDocument; + let params: Record<string, string> = { + doc_id: this.documentId?.toString(), + }; + if (!!this.groupId && !!this.groupUserId) { + params = { + ...params, + user_id: this.groupUserId.toString(), + group_id: this.groupId.toString(), + }; + } + this.provider = new WebsocketProvider( this.ydoc, this.actionCableService.consumer, "ApplicationCable::DocumentChannel", - { doc_id: this.documentId?.toString() } + params ); let awareness = undefined; diff --git a/Frontend Angular 4/src/app/shared/components/destroy-group-modal/destroy-group-modal.component.ts b/Frontend Angular 4/src/app/shared/components/destroy-group-modal/destroy-group-modal.component.ts index 62e1962..f4b4582 100644 --- a/Frontend Angular 4/src/app/shared/components/destroy-group-modal/destroy-group-modal.component.ts +++ b/Frontend Angular 4/src/app/shared/components/destroy-group-modal/destroy-group-modal.component.ts @@ -17,11 +17,12 @@ import { GroupService } from "../../services/group.service"; export class DestroyGroupModal implements OnInit { @Input() groupName: string; @Input() groupId: number; + @Input() userRole: string; /** * Se dispara cuando se quiere administrar los usuarios del grupo */ @Input() confirmGroupDestroyed: () => void; - title = "Destruir Grupo"; + title: string; message: string; constructor( @@ -32,7 +33,13 @@ export class DestroyGroupModal implements OnInit { ngOnInit(): void { this.groupName = `${this.groupName}`; - this.message = `¿Estás seguro de que deseas destruir el grupo ${this.groupName}?`; + if (this.userRole === "owner") { + this.title = "Destruir Grupo"; + this.message = `¿Estás seguro de que deseas destruir el grupo ${this.groupName}?`; + } else { + this.title = "Abandonar Grupo"; + this.message = `¿Estás seguro de que desea abandonar el grupo ${this.groupName}?`; + } this.destroyGroup = this.destroyGroup.bind(this); this.cancel = this.cancel.bind(this); } diff --git a/Frontend Angular 4/src/app/shared/components/index.ts b/Frontend Angular 4/src/app/shared/components/index.ts index d7aadd0..ebf3490 100755 --- a/Frontend Angular 4/src/app/shared/components/index.ts +++ b/Frontend Angular 4/src/app/shared/components/index.ts @@ -12,3 +12,5 @@ export * from "./create-group-modal/create-group-modal.component"; export * from "./manage-group-users-modal/manage-group-users-modal.component"; export * from "./action-confirmation-modal/action-confirmation-modal.component"; export * from "./destroy-group-modal/destroy-group-modal.component"; +export * from "./request-feedback-modal/request-feedback-modal.component"; +export * from "./return-feedback-modal/return-feedback-modal.component"; diff --git a/Frontend Angular 4/src/app/shared/components/request-feedback-modal/request-feedback-modal.component.html b/Frontend Angular 4/src/app/shared/components/request-feedback-modal/request-feedback-modal.component.html new file mode 100644 index 0000000..626e7c6 --- /dev/null +++ b/Frontend Angular 4/src/app/shared/components/request-feedback-modal/request-feedback-modal.component.html @@ -0,0 +1,6 @@ +<app-action-confirmation-modal + [title]="title" + [message]="message" + [confirmAction]="requestFeedback" + [cancelAction]="cancel" +></app-action-confirmation-modal> diff --git a/Frontend Angular 4/src/app/shared/components/request-feedback-modal/request-feedback-modal.component.ts b/Frontend Angular 4/src/app/shared/components/request-feedback-modal/request-feedback-modal.component.ts new file mode 100644 index 0000000..ee43f09 --- /dev/null +++ b/Frontend Angular 4/src/app/shared/components/request-feedback-modal/request-feedback-modal.component.ts @@ -0,0 +1,65 @@ +import { + Component, + ChangeDetectionStrategy, + Input, + OnInit, +} from "@angular/core"; + +import { NgbActiveModal } from "@ng-bootstrap/ng-bootstrap"; + +import { GroupFileService } from "../../services/group-file.service"; + +@Component({ + selector: "app-request-feedback-modal", + templateUrl: "./request-feedback-modal.component.html", + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class RequestFeedbackModal implements OnInit { + // TODO: Hacer todo este modal basándome en lo que ya está que viene del DestroyGroupModal, + // y luego terminar de hacer la llamada y todo en el GroupComponent + @Input() fileName: string; + @Input() groupName: string | null; + @Input() groupId: number; + @Input() fileId: number; + + /** + * Se dispara cuando se quiere administrar los usuarios del grupo + */ + @Input() confirmFeedbackRequested: () => void; + title = "Pedir retroalimentación"; + message: string; + + constructor( + public modal: NgbActiveModal, + private groupFileService: GroupFileService + ) {} + + ngOnInit(): void { + this.fileName = `${this.fileName}`; + + if (!!this.groupName) { + this.groupName = `${this.groupName}`; + this.message = `¿Desea pedir retroalimentación del archivo ${this.fileName} en el grupo ${this.groupName}?`; + } else { + this.message = `¿Desea pedir retroalimentación del archivo ${this.fileName}?`; + } + + this.requestFeedback = this.requestFeedback.bind(this); + this.cancel = this.cancel.bind(this); + } + + requestFeedback(): void { + this.groupFileService.requestFeedback(this.groupId, this.fileId).subscribe({ + next: (_) => { + this.confirmFeedbackRequested(); + + this.modal.close(); + }, + error: (error) => console.log(error), + }); + } + + cancel(): void { + this.modal.dismiss(); + } +} diff --git a/Frontend Angular 4/src/app/shared/components/request-group-file-feedback-modal/request-group-file-feedback-modal.component.html b/Frontend Angular 4/src/app/shared/components/request-group-file-feedback-modal/request-group-file-feedback-modal.component.html new file mode 100644 index 0000000..0451b0c --- /dev/null +++ b/Frontend Angular 4/src/app/shared/components/request-group-file-feedback-modal/request-group-file-feedback-modal.component.html @@ -0,0 +1,6 @@ +<app-action-confirmation-modal + [title]="title" + [message]="message" + [confirmAction]="destroyGroup" + [cancelAction]="cancel" +></app-action-confirmation-modal> diff --git a/Frontend Angular 4/src/app/shared/components/request-group-file-feedback-modal/request-group-file-feedback-modal.component.ts b/Frontend Angular 4/src/app/shared/components/request-group-file-feedback-modal/request-group-file-feedback-modal.component.ts new file mode 100644 index 0000000..1f81dd5 --- /dev/null +++ b/Frontend Angular 4/src/app/shared/components/request-group-file-feedback-modal/request-group-file-feedback-modal.component.ts @@ -0,0 +1,54 @@ +import { + Component, + ChangeDetectionStrategy, + Input, + OnInit, +} from "@angular/core"; + +import { NgbActiveModal } from "@ng-bootstrap/ng-bootstrap"; + +import { GroupService } from "../../services/group.service"; + +@Component({ + selector: "app-destroy-group-modal", + templateUrl: "./destroy-group-modal.component.html", + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class RequestGroupFileModal implements OnInit { + @Input() groupName: string; + @Input() fileName: string; + @Input() groupId: number; + @Input() fileId: number; + /** + * Se dispara cuando se quiere administrar los usuarios del grupo + */ + title: string; + message: string; + + constructor( + public modal: NgbActiveModal, + private groupService: GroupService + ) {} + + ngOnInit(): void { + this.groupName = `${this.groupName}`; + + this.title = "Pedir Retroalimentación"; + this.message = `¿Estás seguro de que desea pedir retroalimentación para el archivo ${this.fileName}? \n Esta decisión no se puede deshacer.`; + this.requestFeedback = this.requestFeedback.bind(this); + this.cancel = this.cancel.bind(this); + } + + requestFeedback(): void { + this.groupService.requestFeedback(this.groupId, this.fileId).subscribe({ + next: (_) => { + this.modal.close(); + }, + error: (error) => console.log(error), + }); + } + + cancel(): void { + this.modal.dismiss(); + } +} diff --git a/Frontend Angular 4/src/app/shared/components/return-feedback-modal/return-feedback-modal.component.html b/Frontend Angular 4/src/app/shared/components/return-feedback-modal/return-feedback-modal.component.html new file mode 100644 index 0000000..8f8bd9d --- /dev/null +++ b/Frontend Angular 4/src/app/shared/components/return-feedback-modal/return-feedback-modal.component.html @@ -0,0 +1,6 @@ +<app-action-confirmation-modal + [title]="title" + [message]="message" + [confirmAction]="returnFeedback" + [cancelAction]="cancel" +></app-action-confirmation-modal> diff --git a/Frontend Angular 4/src/app/shared/components/return-feedback-modal/return-feedback-modal.component.ts b/Frontend Angular 4/src/app/shared/components/return-feedback-modal/return-feedback-modal.component.ts new file mode 100644 index 0000000..550dbe3 --- /dev/null +++ b/Frontend Angular 4/src/app/shared/components/return-feedback-modal/return-feedback-modal.component.ts @@ -0,0 +1,68 @@ +import { + Component, + ChangeDetectionStrategy, + Input, + OnInit, +} from "@angular/core"; + +import { NgbActiveModal } from "@ng-bootstrap/ng-bootstrap"; + +import { GroupFileService } from "../../services/group-file.service"; + +@Component({ + selector: "app-return-feedback-modal", + templateUrl: "./return-feedback-modal.component.html", + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ReturnFeedbackModal implements OnInit { + // TODO: Hacer todo este modal basándome en lo que ya está que viene del DestroyGroupModal, + // y luego terminar de hacer la llamada y todo en el GroupComponent + @Input() fileName: string; + @Input() groupName: string | null; + @Input() groupId: number; + @Input() fileId: number; + @Input() userId: number; + + /** + * Se dispara cuando se quiere administrar los usuarios del grupo + */ + @Input() confirmFeedbackReturned: () => void; + title = "Dar retroalimentación"; + message: string; + + constructor( + public modal: NgbActiveModal, + private groupFileService: GroupFileService + ) {} + + ngOnInit(): void { + this.fileName = `${this.fileName}`; + + if (!!this.groupName) { + this.groupName = `${this.groupName}`; + this.message = `¿Desea dar retroalimentación del archivo ${this.fileName} en el grupo ${this.groupName}?`; + } else { + this.message = `¿Desea dar retroalimentación del archivo ${this.fileName}?`; + } + + this.returnFeedback = this.returnFeedback.bind(this); + this.cancel = this.cancel.bind(this); + } + + returnFeedback(): void { + this.groupFileService + .returnFeedback(this.groupId, this.fileId, this.userId) + .subscribe({ + next: (_) => { + this.confirmFeedbackReturned(); + + this.modal.close(); + }, + error: (error) => console.log(error), + }); + } + + cancel(): void { + this.modal.dismiss(); + } +} diff --git a/Frontend Angular 4/src/app/shared/config.ts b/Frontend Angular 4/src/app/shared/config.ts index 1a9848d..953ce88 100755 --- a/Frontend Angular 4/src/app/shared/config.ts +++ b/Frontend Angular 4/src/app/shared/config.ts @@ -56,3 +56,19 @@ export const GET_GROUPS = SERVER + "/api/v2/groups"; export const CREATE_GROUP = SERVER + "/api/v2/groups"; export const UPDATE_GROUP = SERVER + "/api/v2/groups"; export const DELETE_GROUP = SERVER + "/api/v2/groups"; + +export const GET_GROUP_DOCUMENT = + SERVER + "/api/v2/groups/:group_id/documents/:id"; +export const GET_GROUP_FILE = SERVER + "/api/v2/groups/:group_id/files/:id"; +export const GET_GROUP_FILES = SERVER + "/api/v2/groups/:group_id/files"; +export const GET_GROUP_FILES_LISTS = + SERVER + "/api/v2/groups/:group_id/files/lists"; +export const CREATE_GROUP_FILE = SERVER + "/api/v2/groups/:group_id/files"; +export const UPDATE_GROUP_FILE = SERVER + "/api/v2/groups/:group_id/files"; +export const DELETE_GROUP_FILE = SERVER + "/api/v2/groups/:group_id/files"; +export const GET_GROUP_FEEDBACK_REQUESTED_FILES = + SERVER + "/api/v2/groups/:group_id/feedback_requested_files"; +export const CREATE_GROUP_FILE_FEEDBACK_REQUEST = + SERVER + "/api/v2/groups/:group_id/feedback_requests"; +export const GET_GROUP_USER_ROLE = + SERVER + "/api/v2/groups/:group_id/users/:user_id/role"; diff --git a/Frontend Angular 4/src/app/shared/objects/archivo-types.ts b/Frontend Angular 4/src/app/shared/objects/archivo-types.ts index 15e62bc..111b577 100755 --- a/Frontend Angular 4/src/app/shared/objects/archivo-types.ts +++ b/Frontend Angular 4/src/app/shared/objects/archivo-types.ts @@ -22,9 +22,11 @@ export class Archivo { eliminado: boolean; evaluacion: Evaluacion; documentId: number; + groupId?: number; fileId?: number; users?: User[]; role?: string; + feedbackRequested?: boolean; constructor() {} } @@ -39,8 +41,10 @@ export class MFile { parent_id?: number; directory: boolean; children?: MFile[]; + group_id?: number; users?: User[]; role?: string; + feedback_requested?: boolean; constructor() {} } diff --git a/Frontend Angular 4/src/app/shared/services/document.service.ts b/Frontend Angular 4/src/app/shared/services/document.service.ts index 9883de7..4978249 100644 --- a/Frontend Angular 4/src/app/shared/services/document.service.ts +++ b/Frontend Angular 4/src/app/shared/services/document.service.ts @@ -114,10 +114,17 @@ export class DocumentService { }; } - createDocument(document: MDocument): Observable<HttpResponse<MDocument>> { + createDocument( + document: MDocument, + groupId?: number + ): Observable<HttpResponse<MDocument>> { + const params = { ...document }; + if (groupId) { + params["group_id"] = groupId; + } return this.http.post<MDocument>( CREATE_DOCUMENT, - { document }, + { document: params }, { observe: "response", headers: this.postHeaders(), diff --git a/Frontend Angular 4/src/app/shared/services/file.service.ts b/Frontend Angular 4/src/app/shared/services/file.service.ts index f1d56c3..e67468b 100644 --- a/Frontend Angular 4/src/app/shared/services/file.service.ts +++ b/Frontend Angular 4/src/app/shared/services/file.service.ts @@ -107,6 +107,8 @@ export class FileService { archivo.evaluacion = null; archivo.users = file.users; archivo.role = file.role; + archivo.groupId = file.group_id; + archivo.feedbackRequested = file.feedback_requested; if (!!file.children) { archivo.archivos = file.children.map<Archivo>((child) => { @@ -145,6 +147,8 @@ export class FileService { children: [], directory: archivo.directorio, role: archivo.role, + group_id: archivo.groupId, + feedback_requested: archivo.feedbackRequested, }; file.children = archivo.archivos.map<MFile>((archivoHijo) => { diff --git a/Frontend Angular 4/src/app/shared/services/group-document.service.ts b/Frontend Angular 4/src/app/shared/services/group-document.service.ts new file mode 100644 index 0000000..b1df8dd --- /dev/null +++ b/Frontend Angular 4/src/app/shared/services/group-document.service.ts @@ -0,0 +1,311 @@ +import { throwError as observableThrowError, Observable } from "rxjs"; +import { Injectable } from "@angular/core"; +import { Router } from "@angular/router"; +import { + HttpClient, + HttpHeaders, + HttpParams, + HttpResponse, +} from "@angular/common/http"; +import { + Archivo, + Evaluacion, + MFile, + MDocument, +} from "../objects/archivo-types"; +import { Group } from "../objects/grupo"; + +import { SERVER } from "../config"; +import { TranslateService } from "@ngx-translate/core"; +import { AuthenticationService } from "./authentication.service"; +import { catchError } from "rxjs/operators"; +import { GET_GROUP_DOCUMENT } from "../config"; + +@Injectable() +export class GroupDocumentService { + translateService: any; + + /** + * Creates a new DocumentService with the injected HttpClient. + * @param {HttpClient} http - The injected HttpClient. + * @constructor + */ + constructor( + private http: HttpClient, + private router: Router, + private authService: AuthenticationService, + public translate: TranslateService + ) { + this.translateService = translate; + } + + private getHeaders() { + return new HttpHeaders({ + "Content-Type": "application/json", + Authorization: "Bearer " + this.authService.getToken(), + }); + } + + private postHeaders() { + return new HttpHeaders({ + "Content-Type": "application/json", + Accept: "application/json", + "Access-Control-Allow-Headers": "Content-Type", + }); + } + + // getDocuments(ids: number[]): Observable<{ documents: MDocument[] }> { + // const params = new HttpParams({ + // fromObject: { "document_ids[]": ids }, + // }); + // return this.http + // .get<{ documents: MDocument[] }>(GET_DOCUMENTS, { + // headers: this.getHeaders(), + // params: params, + // }) + // .pipe(catchError(this.handleError)); + // } + + getGroupDocument( + groupId: number, + id: number, + userId: number + ): Observable<{ document: MDocument }> { + let params = {}; + if (!!userId) { + params = new HttpParams().set("user_id", userId); + } + return this.http + .get<{ document: MDocument }>( + GET_GROUP_DOCUMENT.replace(/:group_id/g, String(groupId)).replace( + /:id/g, + String(id) + ), + { + headers: this.getHeaders(), + params: params, + } + ) + .pipe(catchError(this.handleError)); + } + + // documentToArchivo(document: MDocument): Archivo { + // return { + // id: document.id, + // documentId: document.id, + // nombre: document.title, + // contenido: document.text_doc, + // fechaCreacion: document.created_at, + // cedulaCreador: document.users[0]?.email, + // editable: true, + // padreId: document.parent_id ? document.parent_id : -1, + // fileId: document.file_id, + // archivoOrigenId: null, + // archivos: [], + // directorio: false, + // estado: "activo", + // eliminado: false, + // evaluacion: null, + // users: document.users, + // role: document.role, + // }; + // } + + // archivoToDocument(archivo: Archivo): MDocument { + // return { + // id: archivo.id, + // title: archivo.nombre, + // text_doc: archivo.contenido, + // created_at: archivo.fechaCreacion, + // parent_id: archivo.padreId, + // role: archivo.role, + // users: [], + // }; + // } + + // createDocument( + // document: MDocument, + // groupId?: number + // ): Observable<HttpResponse<MDocument>> { + // const params = { ...document }; + // if (groupId) { + // params["group_id"] = groupId; + // } + // return this.http.post<MDocument>( + // CREATE_DOCUMENT, + // { document: params }, + // { + // observe: "response", + // headers: this.postHeaders(), + // } + // ); + // } + + // // Legacy method, need to check if it's still used + // getArchivos(cedula: string): Observable<Archivo[]> { + // let headers = this.getHeaders(); + // let params: HttpParams = new HttpParams(); + // params = params.set("cedula", cedula); + // let httpOptions = { headers: headers, params: params }; + + // return this.http + // .get<Archivo[]>(SERVER + "/servicios/archivo", httpOptions) + // .pipe(catchError(this.handleError)); + // } + + // // Legacy method, need to check if it's still used + // getArchivosCompartidosAlumno(cedula: string): Observable<Archivo[]> { + // let headers = this.getHeaders(); + // let params: HttpParams = new HttpParams(); + // params = params.set("cedula", cedula); + // params = params.set("compartidos", "true"); + // let httpOptions = { headers: headers, params: params }; + + // return this.http + // .get<Archivo[]>(SERVER + "/servicios/archivo", httpOptions) + // .pipe(catchError(this.handleError)); + // } + + // createFile( + // file_title, + // file_binary_doc_text + // ): Observable<HttpResponse<Object>> { + // return this.http.post<Object>( + // CREATE_DOCUMENT, + // { + // document: { title: file_title, text_doc: file_binary_doc_text }, + // }, + // { + // observe: "response", + // headers: this.postHeaders(), + // } + // ); + // } + + // updateDocument( + // file_id, + // file_title, + // save_document_content = false + // ): Observable<HttpResponse<Object>> { + // return this.http.patch<Object>( + // `${UPDATE_DOCUMENT}/${file_id}`, + // { + // document: { + // title: file_title, + // save_document_content: save_document_content, + // }, + // }, + // { + // observe: "response", + // headers: this.postHeaders(), + // } + // ); + // } + + // destroyAuxDocument(): Observable<HttpResponse<Object>> { + // return this.http.delete<Object>(DESTROY_AUX_DOCUMENT, { + // observe: "response", + // headers: this.postHeaders(), + // }); + // } + + // // Legacy method, need to check if it's still used + // eliminarArchivo(archivoId): Observable<Response> { + // let headers = this.getHeaders(); + // let httpOptions = { headers: headers }; + + // return this.http + // .delete<Response>(SERVER + "/servicios/archivo/" + archivoId, httpOptions) + // .pipe(catchError(this.handleError)); + // } + + // // Legacy method, need to check if it's still used + // getCopiaArchivoCompartidoGrupo(cedula, archivoId): Observable<Archivo> { + // let headers = this.getHeaders(); + // let params: HttpParams = new HttpParams(); + // params = params.set("cedula", cedula); + // let httpOptions = { headers: headers, params: params }; + + // return this.http + // .get<Archivo>( + // SERVER + "/servicios/archivo/compartido/" + archivoId, + // httpOptions + // ) + // .pipe(catchError(this.handleError)); + // } + + // // Legacy method, need to check if it's still used + // compartirArchivoGrupo(grupo, archivoId): Observable<Archivo> { + // let headers = this.getHeaders(); + // let httpOptions = { headers: headers }; + // var archId = { + // id: archivoId, + // }; + // return this.http + // .post<Archivo>( + // SERVER + + // "/servicios/grupo/" + + // grupo.liceoId + + // "/" + + // grupo.anio + + // "/" + + // grupo.grado + + // "/" + + // grupo.grupo + + // "/archivo", + // archId, + // httpOptions + // ) + // .pipe(catchError(this.handleError)); + // } + + // // Legacy method, need to check if it's still used + // calificarArchivo(archivoId, estado, evaluacion): Observable<Evaluacion> { + // let headers = this.getHeaders(); + // let httpOptions = { headers: headers }; + // return this.http + // .post<Evaluacion>( + // SERVER + + // "/servicios/archivo/" + + // archivoId + + // "/" + + // estado + + // "/evaluacion", + // evaluacion, + // httpOptions + // ) + // .pipe(catchError(this.handleError)); + // } + + // // Legacy method, need to check if it's still used + // getGrupos(cedula: string): Observable<Group[]> { + // let headers = this.getHeaders(); + // let params: HttpParams = new HttpParams(); + // params = params.set("cedula", cedula); + // let httpOptions = { headers: headers, params: params }; + + // return this.http + // .get<Group[]>(SERVER + "/servicios/grupo", httpOptions) + // .pipe(catchError(this.handleError)); + // } + + /** + * Handle HTTP error + */ + private handleError(error: any) { + if (error.status == 401) { + this.translateService + .get("i18n.code") + .subscribe((res) => this.router.navigate(["/" + res + "/login"])); + } + // In a real world app, we might use a remote logging infrastructure + // We'd also dig deeper into the error to get a better message + let errMsg = error.message + ? error.message + : error.status + ? `${error.status} - ${error.statusText}` + : "Server error"; + console.error(errMsg); // log to console instead + return observableThrowError(errMsg); + } +} diff --git a/Frontend Angular 4/src/app/shared/services/group-file.service.ts b/Frontend Angular 4/src/app/shared/services/group-file.service.ts new file mode 100644 index 0000000..210aef5 --- /dev/null +++ b/Frontend Angular 4/src/app/shared/services/group-file.service.ts @@ -0,0 +1,264 @@ +import { throwError as observableThrowError, Observable } from "rxjs"; +import { Injectable } from "@angular/core"; +import { Router } from "@angular/router"; +import { + HttpClient, + HttpHeaders, + HttpParams, + HttpResponse, +} from "@angular/common/http"; +import { Archivo, Evaluacion, MFile } from "../objects/archivo-types"; + +import { TranslateService } from "@ngx-translate/core"; +import { AuthenticationService } from "./authentication.service"; +import { catchError } from "rxjs/operators"; +import { + CREATE_GROUP_FILE, + UPDATE_GROUP_FILE, + DELETE_GROUP_FILE, + GET_GROUP_FILE, + GET_GROUP_FILES, + GET_GROUP_FILES_LISTS, + GET_GROUP_FEEDBACK_REQUESTED_FILES, +} from "../config"; + +@Injectable() +export class GroupFileService { + translateService: any; + + /** + * Creates a new FileService with the injected HttpClient. + * @param {HttpClient} http - The injected HttpClient. + * @constructor + */ + constructor( + private http: HttpClient, + private router: Router, + private authService: AuthenticationService, + public translate: TranslateService + ) { + this.translateService = translate; + } + + private getHeaders() { + return new HttpHeaders({ + "Content-Type": "application/json", + Authorization: "Bearer " + this.authService.getToken(), + }); + } + + private postHeaders() { + return new HttpHeaders({ + "Content-Type": "application/json", + Accept: "application/json", + "Access-Control-Allow-Headers": "Content-Type", + }); + } + + getGroupFile(groupId: number, id: number): Observable<{ file: MFile }> { + return this.http + .get<{ file: MFile }>( + GET_GROUP_FILE.replace(/:group_id/g, String(groupId)).replace( + /:id/g, + String(id) + ), + { + headers: this.getHeaders(), + } + ) + .pipe(catchError(this.handleError)); + } + + getSpecificUserFileWithSetGroup(groupId: number, userId: number) { + return { + getFile: (id: number) => { + return this.getSpecificUserGroupFile(groupId, userId, id); + }, + }; + } + + getGroupFiles(groupId: number): Observable<MFile[]> { + return this.http + .get<MFile[]>(GET_GROUP_FILES.replace(/:group_id/g, String(groupId)), { + headers: this.getHeaders(), + }) + .pipe(catchError(this.handleError)); + } + + getSpecificUserGroupFiles( + groupId: number, + userId: number + ): Observable<MFile[]> { + return this.http + .get<MFile[]>(GET_GROUP_FILES.replace(/:group_id/g, String(groupId)), { + headers: this.getHeaders(), + params: new HttpParams().set("user_id", userId), + }) + .pipe(catchError(this.handleError)); + } + + getSpecificUserGroupFile( + groupId: number, + userId: number, + fileId: number + ): Observable<{ file: MFile }> { + return this.http + .get<{ file: MFile }>( + GET_GROUP_FILE.replace(/:group_id/g, String(groupId)).replace( + /:id/g, + String(fileId) + ), + { + headers: this.getHeaders(), + params: new HttpParams().set("user_id", userId), + } + ) + .pipe(catchError(this.handleError)); + } + + getGroupFilesList( + groupId: number, + options = {} + ): Observable<{ files: MFile[] }> { + const siblingsOfId = options["siblingsOfId"]; + return this.http + .get<{ files: MFile[] }>( + GET_GROUP_FILES_LISTS.replace(/:group_id/g, String(groupId)), + { + headers: this.getHeaders(), + params: siblingsOfId + ? new HttpParams().set("siblings_of_id", siblingsOfId) + : null, + } + ) + .pipe(catchError(this.handleError)); + } + + getFeedbackRequestedGroupFiles( + groupId: number, + userId?: number + ): Observable<MFile[]> { + let params = {}; + if (userId) { + params["user_id"] = userId; + } + return this.http + .get<MFile[]>( + GET_GROUP_FEEDBACK_REQUESTED_FILES.replace( + /:group_id/g, + String(groupId) + ), + { + headers: this.getHeaders(), + params: params, + } + ) + .pipe(catchError(this.handleError)); + } + + createGroupFile( + groupId: number, + file: MFile + ): Observable<HttpResponse<Object>> { + return this.http.post<Object>( + CREATE_GROUP_FILE.replace(/:group_id/g, String(groupId)), + { + file: { + title: file.title, + parent_id: file.parent_id, + directory: file.directory, + }, + }, + { + observe: "response", + headers: this.postHeaders(), + } + ); + } + + updateGroupFile( + groupId: number, + file_id, + file_parent_id, + users + ): Observable<HttpResponse<Object>> { + const update_params = {}; + if (file_parent_id) { + update_params["parent_id"] = file_parent_id; + } + if (users) { + users = users.map((user) => { + return { ...user, role: user.role.toLowerCase() }; + }); + update_params["users"] = users; + } + + return this.http.patch<Object>( + `${UPDATE_GROUP_FILE.replace(/:group_id/g, String(groupId))}/${file_id}`, + { file: update_params }, + { + observe: "response", + headers: this.postHeaders(), + } + ); + } + + requestFeedback( + groupId: number, + fileId: number + ): Observable<HttpResponse<Object>> { + return this.http.patch<Object>( + `${UPDATE_GROUP_FILE.replace(/:group_id/g, String(groupId))}/${fileId}`, + { file: { feedback_requested: true } }, + { + observe: "response", + headers: this.postHeaders(), + } + ); + } + + returnFeedback( + groupId: number, + fileId: number, + userId: number + ): Observable<HttpResponse<Object>> { + return this.http.patch<Object>( + `${UPDATE_GROUP_FILE.replace(/:group_id/g, String(groupId))}/${fileId}`, + { file: { feedback_requested: false }, user_id: userId }, + { + observe: "response", + headers: this.postHeaders(), + } + ); + } + + deleteGroupFile(groupId: number, file_id): Observable<HttpResponse<Object>> { + return this.http.delete<Object>( + `${DELETE_GROUP_FILE.replace(/:group_id/g, String(groupId))}/${file_id}`, + { + observe: "response", + headers: this.postHeaders(), + } + ); + } + + /** + * Handle HTTP error + */ + private handleError(error: any) { + if (error.status == 401) { + this.translateService + .get("i18n.code") + .subscribe((res) => this.router.navigate(["/" + res + "/login"])); + } + // In a real world app, we might use a remote logging infrastructure + // We'd also dig deeper into the error to get a better message + let errMsg = error.message + ? error.message + : error.status + ? `${error.status} - ${error.statusText}` + : "Server error"; + console.error(errMsg); // log to console instead + return observableThrowError(errMsg); + } +} diff --git a/Frontend Angular 4/src/app/shared/services/group.service.ts b/Frontend Angular 4/src/app/shared/services/group.service.ts index f1335f8..bf3e8d8 100644 --- a/Frontend Angular 4/src/app/shared/services/group.service.ts +++ b/Frontend Angular 4/src/app/shared/services/group.service.ts @@ -19,6 +19,8 @@ import { DELETE_GROUP, GET_GROUP, GET_GROUPS, + CREATE_GROUP_FILE_FEEDBACK_REQUEST, + GET_GROUP_USER_ROLE, } from "../config"; @Injectable() @@ -210,6 +212,48 @@ export class GroupService { // }); // } + requestFeedback( + groupId: number, + fileId: number + ): Observable<HttpResponse<Object>> { + return this.http.patch<Object>( + `${CREATE_GROUP_FILE_FEEDBACK_REQUEST.replace( + /:group_id/g, + String(groupId) + )}`, + { feedback_request: { file_id: fileId } }, + { + observe: "response", + headers: this.postHeaders(), + } + ); + } + + getUserRole( + groupId: number, + userId: number + ): Observable<{ role: { role: string } }> { + return this.http + .get<{ role: { role: string } }>( + GET_GROUP_USER_ROLE.replace(/:group_id/g, String(groupId)).replace( + /:user_id/g, + String(userId) + ), + { + headers: this.getHeaders(), + } + ) + .pipe(catchError(this.handleError)); + } + + userCanEditAnyGroupFile(role: string): boolean { + return role === "owner" || role === "moderator" || role === "manager"; + } + + userCanReturnFeedback(role: string): boolean { + return this.userCanEditAnyGroupFile(role); + } + /** * Handle HTTP error */ diff --git a/Frontend Angular 4/src/app/shared/shared.module.ts b/Frontend Angular 4/src/app/shared/shared.module.ts index 06214de..8012c63 100644 --- a/Frontend Angular 4/src/app/shared/shared.module.ts +++ b/Frontend Angular 4/src/app/shared/shared.module.ts @@ -3,22 +3,26 @@ import { MainButtonComponent } from "./components/main-button/main-button.compon import { TitleCaseModule } from "../shared/modules/titlecase.module"; import { I18nModule } from "../shared/modules/translate/i18n.module"; import { CommonModule } from "@angular/common"; -import { CreateFileModal } from "./components"; -import { CreateGroupModal } from "./components"; -import { ImportFileModal } from "./components"; -import { ShareFileModal } from "./components"; -import { ManageGroupUsersModal } from "./components"; import { FormsModule } from "@angular/forms"; -import { FolderInput } from "./components"; -import { SelectComponent } from "./components"; -import { ChipInputComponent } from "./components"; import { MatFormFieldModule } from "@angular/material/form-field"; import { MatChipsModule } from "@angular/material/chips"; import { MatIconModule } from "@angular/material/icon"; import { NgbDropdownModule } from "@ng-bootstrap/ng-bootstrap"; -import { AddUsersModal } from "./components"; -import { ActionConfirmationModal } from "./components"; -import { DestroyGroupModal } from "./components"; +import { + ActionConfirmationModal, + AddUsersModal, + ChipInputComponent, + CreateFileModal, + CreateGroupModal, + DestroyGroupModal, + FolderInput, + ImportFileModal, + ManageGroupUsersModal, + RequestFeedbackModal, + ReturnFeedbackModal, + SelectComponent, + ShareFileModal, +} from "./components"; @NgModule({ imports: [ TitleCaseModule, @@ -43,6 +47,8 @@ import { DestroyGroupModal } from "./components"; AddUsersModal, ActionConfirmationModal, DestroyGroupModal, + RequestFeedbackModal, + ReturnFeedbackModal, ], exports: [MainButtonComponent, CreateFileModal], }) diff --git a/Frontend Angular 4/src/assets/i18n/en.json b/Frontend Angular 4/src/assets/i18n/en.json index 246f220..a33b35e 100755 --- a/Frontend Angular 4/src/assets/i18n/en.json +++ b/Frontend Angular 4/src/assets/i18n/en.json @@ -33,7 +33,9 @@ "close": "close", "qualify": "qualify", "confirm": "confirm", - "signup": "sign up" + "signup": "sign up", + "requestFeedback": "Request feedback", + "returnFeedback": "Return feedback" }, "links": { "register": "Create account", diff --git a/Frontend Angular 4/src/assets/i18n/es.json b/Frontend Angular 4/src/assets/i18n/es.json index 44ac256..e3737b6 100755 --- a/Frontend Angular 4/src/assets/i18n/es.json +++ b/Frontend Angular 4/src/assets/i18n/es.json @@ -33,7 +33,9 @@ "close": "cerrar", "qualify": "calificar", "confirm": "confirmar", - "signup": "registrarse" + "signup": "registrarse", + "requestFeedback": "Solicitar retroalimentación", + "returnFeedback": "Devolver retroalimentación" }, "links": { "register": "Registrarse", diff --git a/Frontend Angular 4/src/styles/console.css b/Frontend Angular 4/src/styles/console.css index 4a44020..6441af3 100755 --- a/Frontend Angular 4/src/styles/console.css +++ b/Frontend Angular 4/src/styles/console.css @@ -70,7 +70,7 @@ .nomArchivoInp { /*width: 55% !important;*/ - width: calc(100% - 315px); + width: calc(100% - 345px); float: left; } -- GitLab From 3d091ccdaa52f75d50d092f501fb4cb203c3f3b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rodr=C3=ADguez?= <diego.rodriguez@rootstrap.com> Date: Thu, 6 Feb 2025 18:08:48 -0300 Subject: [PATCH 4/9] wip: User can assign files --- .../app/layout/grupos/grupos.component.html | 31 ++++ .../src/app/layout/grupos/grupos.component.ts | 133 +++++++++++++++++- .../assign-file-modal.component.html | 33 +++++ .../assign-file-modal.component.ts | 79 +++++++++++ .../checkbox-select.component.html | 33 +++++ .../checkbox-select.component.ts | 64 +++++++++ .../src/app/shared/components/index.ts | 2 + Frontend Angular 4/src/app/shared/config.ts | 2 + .../src/app/shared/objects/archivo-types.ts | 4 + .../app/shared/services/group-file.service.ts | 20 +++ .../src/app/shared/services/group.service.ts | 4 + .../src/app/shared/shared.module.ts | 6 + 12 files changed, 404 insertions(+), 7 deletions(-) create mode 100644 Frontend Angular 4/src/app/shared/components/assign-file-modal/assign-file-modal.component.html create mode 100644 Frontend Angular 4/src/app/shared/components/assign-file-modal/assign-file-modal.component.ts create mode 100644 Frontend Angular 4/src/app/shared/components/checkbox-select/checkbox-select.component.html create mode 100644 Frontend Angular 4/src/app/shared/components/checkbox-select/checkbox-select.component.ts diff --git a/Frontend Angular 4/src/app/layout/grupos/grupos.component.html b/Frontend Angular 4/src/app/layout/grupos/grupos.component.html index 74c0e81..8c5625d 100755 --- a/Frontend Angular 4/src/app/layout/grupos/grupos.component.html +++ b/Frontend Angular 4/src/app/layout/grupos/grupos.component.html @@ -323,6 +323,18 @@ > <i aria-hidden="true" class="fa fa-exchange"></i> </button> + <button + *ngIf="canAssignFile()" + ngbPopover="{{ + 'i18n.action.assignFile' | translate | titleCase + }} {{ 'i18n.object.file' | translate | titleCase }}" + placement="bottom" + triggers="mouseenter:mouseleave" + class="btn btn-sm btn-secondary pull-left mr-2" + (click)="openAssignFileModal()" + > + <i aria-hidden="true" class="fa fa-share"></i> + </button> <button *ngIf="canReturnFeedback()" ngbPopover="{{ @@ -411,3 +423,22 @@ *ngIf="modalMoveFile" > </matefun-modal-seleccionar-directorio> + +<matefun-modal-borrar-archivo + bodyDescription="{{ 'i18n.msg.file.delete' | translate : fileNameToRemove }}" + cancelLabel="{{ 'i18n.action.cancel' | translate | titleCase }}" + confirmLabel="{{ 'i18n.action.delete' | translate | titleCase }}" + header="{{ 'i18n.action.delete' | translate | titleCase }} + {{ + (modalTypeIsFile ? 'i18n.object.file' : 'i18n.object.folder') + | translate + | titleCase + }}" + [opened]="modalRemoveFileOpened" + [typeOfFile]="modalTypeIsFile ? 'file' : 'directory'" + (close)="modalRemoveFile = false" + (cancelAction)="modalRemoveFileOpened = false" + (removeFile)="confirmFileDeletion()" + *ngIf="modalRemoveFile" +> +</matefun-modal-borrar-archivo> diff --git a/Frontend Angular 4/src/app/layout/grupos/grupos.component.ts b/Frontend Angular 4/src/app/layout/grupos/grupos.component.ts index 02c939e..25adddd 100755 --- a/Frontend Angular 4/src/app/layout/grupos/grupos.component.ts +++ b/Frontend Angular 4/src/app/layout/grupos/grupos.component.ts @@ -31,6 +31,7 @@ import { DestroyGroupModal, RequestFeedbackModal, ReturnFeedbackModal, + AssignFileModal, } from "app/shared"; const STARTS_WITH_CAPITAL_LETTER_REGEX = /^[A-Z]/; @@ -139,6 +140,10 @@ export class GruposComponent { */ modalMoveFileOpened = true; + fileNameToRemove: { fileName: string } = { fileName: "" }; + modalRemoveFile = false; + modalRemoveFileOpened = true; + constructor( private router: Router, private authService: AuthenticationService, @@ -180,6 +185,8 @@ export class GruposComponent { this.confirmGroupCreation = this.confirmGroupCreation.bind(this); this.confirmGroupUpdated = this.confirmGroupUpdated.bind(this); this.confirmGroupDestroyed = this.confirmGroupDestroyed.bind(this); + this.confirmFeedbackRequsted = this.confirmFeedbackRequsted.bind(this); + this.confirmFeedbackReturned = this.confirmFeedbackReturned.bind(this); this.loading = true; // this.haskellService.getGrupos(cedula).subscribe( @@ -373,16 +380,43 @@ export class GruposComponent { this.createModalRef = this.modalService.open(ReturnFeedbackModal); + this.createModalRef.componentInstance.confirmFeedbackReturned = + this.confirmFeedbackReturned; this.createModalRef.componentInstance.groupName = null; - this.createModalRef.componentInstance.groupId = this.grupoSeleccionado; + this.createModalRef.componentInstance.groupId = this.grupoSeleccionado.id; this.createModalRef.componentInstance.fileName = this.selectedFile.nombre; this.createModalRef.componentInstance.fileId = this.selectedFile.fileId; - this.createModalRef.componentInstance.userId = this.selectedUser.id; - this.createModalRef.componentInstance.confirmFeedbackReturned = - this.confirmFeedbackReturned; + this.createModalRef.componentInstance.userId = + this.selectedUser?.id || + JSON.parse(localStorage.getItem("currentUser"))["id"]; } - confirmFeedbackReturned() {} + confirmFeedbackReturned() { + if (!!this.selectedUser) { + this.selectedFile = null; + this.archivoSeleccionado = null; + this.seleccionarUser(this.selectedUser); + } else { + this.loadFilesAndFolders( + this.grupoSeleccionado.id, + this.directorioActual.id + ); + } + // let selectedFile = this.selectedUserDirectorioActual?.archivos?.find( + // (archivo) => archivo.documentId === this.selectedFile.id + // ); + // if (!!selectedFile) { + // this.seleccionarUser(this.selectedUser); + // selectedFile.feedbackRequested = false; + // } else { + // selectedFile = this.directorioActual.archivos.find( + // (archivo) => archivo.documentId === this.selectedFile.id + // ); + // if (!!selectedFile) { + // selectedFile.feedbackRequested = false; + // } + // } + } seleccionarArchivo(archivo) { this.selectedFile = archivo; @@ -421,8 +455,11 @@ export class GruposComponent { * @param archivo Archivo a seleccionar */ actualizarArchivoSeleccionado(archivo: Archivo) { + if (!!this.selectedFile) { + archivo.feedbackRequested = this.selectedFile.feedbackRequested; + } this.archivoSeleccionado = archivo; - this.selectedFile = archivo; //Probando + this.selectedFile = archivo; //Aquí el problema, se necesita para los que pidieron feedback, pero jode para los archivos propios this.preview = !!archivo?.contenido ? archivo.contenido : ""; } @@ -472,6 +509,7 @@ export class GruposComponent { const document = data["document"]; const archivoDocument = this.documentService.documentToArchivo(document); + archivoDocument.feedbackRequested = true; this.actualizarArchivoSeleccionado(archivoDocument); }, (error) => console.log(error) @@ -518,7 +556,54 @@ export class GruposComponent { this.confirmFeedbackRequsted; } - confirmFeedbackRequsted() {} + confirmFeedbackRequsted() { + this.archivoSeleccionado.feedbackRequested = true; + this.selectedFile.feedbackRequested = true; + const fileInDirectory = this.directorioActual.archivos.find( + (archivo) => archivo.documentId === this.archivoSeleccionado.id + ); + if (!!fileInDirectory) { + fileInDirectory.feedbackRequested = true; + } + this.changeDetectorRef.detectChanges(); + } + + mostrarEliminarDialogo() { + // Chequear si se seleccionó un archivo + if (!this.archivoSeleccionado) { + this.showNotification("warning", "i18n.warning.file.noSelected"); + return; + } + + this.fileNameToRemove = { + fileName: this.archivoSeleccionado?.nombre || this.selectedFile?.nombre, + }; + + // Determina el tipo de modal a renderizar + this.modalTypeIsFile = !this.archivoSeleccionado.directorio; + + // Mostrar el modal + this.modalRemoveFile = true; + this.modalRemoveFileOpened = true; + } + + confirmFileDeletion() { + this.groupFileService + .deleteGroupFile( + this.grupoSeleccionado.id, + this.archivoSeleccionado?.fileId || this.selectedFile?.fileId + ) + .subscribe( + (_) => { + this.modalRemoveFileOpened = false; + this.loadFilesAndFolders( + this.grupoSeleccionado.id, + this.directorioActual.id + ); + }, + (error) => console.log(error) + ); + } openDestroyGroupModal() { if (!this.grupoSeleccionado) { @@ -553,6 +638,40 @@ export class GruposComponent { // ); } + openAssignFileModal() { + if (!this.grupoSeleccionado) { + this.showNotification("warning", "i18n.warning.group.noSelected"); + return; + } + + if (!this.selectedFile.fileId) { + this.showNotification("warning", "i18n.warning.file.noSelected"); + return; + } + + this.createModalRef = this.modalService.open(AssignFileModal); + + this.createModalRef.componentInstance.groupName = null; + this.createModalRef.componentInstance.groupId = this.grupoSeleccionado.id; + this.createModalRef.componentInstance.fileName = this.selectedFile.nombre; + this.createModalRef.componentInstance.fileId = this.selectedFile.fileId; + this.createModalRef.componentInstance.users = this.grupoSeleccionado.users; + this.createModalRef.componentInstance.userFilter = "role"; + this.createModalRef.componentInstance.confirmFileAssigned = + this.confirmFileAssigned; + } + + confirmFileAssigned() {} + + canAssignFile() { + return ( + !!this.grupoSeleccionado && + !!this.selectedFile && + !this.selectedUser && + this.groupService.userCanAssignFiles(this.grupoSeleccionado.role) + ); + } + confirmGroupDestroyed() { this.desseleccionarGrupo(); this.loadGroups(); diff --git a/Frontend Angular 4/src/app/shared/components/assign-file-modal/assign-file-modal.component.html b/Frontend Angular 4/src/app/shared/components/assign-file-modal/assign-file-modal.component.html new file mode 100644 index 0000000..a8a4e23 --- /dev/null +++ b/Frontend Angular 4/src/app/shared/components/assign-file-modal/assign-file-modal.component.html @@ -0,0 +1,33 @@ +<div class="modal-header"> + <span class="modal-title font-bold" id="modal-title">{{ title }}</span> + <button + aria-label="Cerrar diálogo" + (click)="modal.dismiss('Close assign file modal')" + part="close-button" + class="close-button fa fa-close" + type="button" + ></button> +</div> +<div class="modal-body"> + <div class="flex flex-col justify-between w-full"> + <label for="new-users">Users</label> + <div class="flex justify-between w-full items-center"> + <app-checkbox-select + [(entities)]="users" + entitiesName="user" + filterName="role" + class="w-full" + ></app-checkbox-select> + </div> + </div> + <div class="flex justify-end mt-2"> + <button + (click)="assignFile()" + [disabled]="selectedUsers().length === 0" + class="btn btn-primary" + slot="primary" + > + Asignar + </button> + </div> +</div> diff --git a/Frontend Angular 4/src/app/shared/components/assign-file-modal/assign-file-modal.component.ts b/Frontend Angular 4/src/app/shared/components/assign-file-modal/assign-file-modal.component.ts new file mode 100644 index 0000000..d7136f2 --- /dev/null +++ b/Frontend Angular 4/src/app/shared/components/assign-file-modal/assign-file-modal.component.ts @@ -0,0 +1,79 @@ +import { + Component, + ChangeDetectionStrategy, + Input, + OnInit, + ChangeDetectorRef, +} from "@angular/core"; + +import { NgbActiveModal } from "@ng-bootstrap/ng-bootstrap"; + +import { GroupFileService } from "../../services/group-file.service"; + +import { selectableUser } from "../../objects/archivo-types"; + +@Component({ + selector: "app-assign-file-modal", + templateUrl: "./assign-file-modal.component.html", + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class AssignFileModal implements OnInit { + @Input() fileName: string; + @Input() groupName: string | null; + @Input() groupId: number; + @Input() fileId: number; + + @Input() users: selectableUser[] = []; + @Input() userFilter: string = null; + + /** + * Se dispara cuando se quiere administrar los usuarios del grupo + */ + @Input() confirmFileAssigned: () => void; + title = "Asignar archivo"; + + constructor( + public modal: NgbActiveModal, + private groupFileService: GroupFileService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + ngOnInit(): void { + this.fileName = `${this.fileName}`; + + if (!!this.groupName) { + this.groupName = `${this.groupName}`; + this.title = `¿Asignar archivo ${this.fileName} en el grupo ${this.groupName}?`; + } else { + this.title = `¿Asignar archivo ${this.fileName}?`; + } + + this.assignFile = this.assignFile.bind(this); + this.cancel = this.cancel.bind(this); + } + + selectedUsers() { + return this.users.filter((user) => user.selected); + } + + assignFile(): void { + this.groupFileService + .assignFile( + this.groupId, + this.fileId, + this.users.filter((user) => user.selected).map((user) => user.id), + [] // subgroups.map((subgroup) => subgroup.id) + ) + .subscribe({ + next: (_) => { + this.confirmFileAssigned(); + this.modal.close(); + }, + error: (error) => console.log(error), + }); + } + + cancel(): void { + this.modal.dismiss(); + } +} diff --git a/Frontend Angular 4/src/app/shared/components/checkbox-select/checkbox-select.component.html b/Frontend Angular 4/src/app/shared/components/checkbox-select/checkbox-select.component.html new file mode 100644 index 0000000..7b0dd18 --- /dev/null +++ b/Frontend Angular 4/src/app/shared/components/checkbox-select/checkbox-select.component.html @@ -0,0 +1,33 @@ +<div class="flex flex-col justify-between w-full"> + <label for="new-users">Something</label> +</div> +<table class="table-fixed w-96 mx-auto"> + <thead> + <tr> + <th> + <mat-checkbox + [checked]="allEntitiesSelected()" + (change)="onCheckboxChange($event.checked)" + > + </mat-checkbox> + </th> + <th>{{ entitiesName }}</th> + <th *ngIf="{filterName}">{{ filterName }}</th> + </tr> + </thead> + <tbody> + <tr *ngFor="let entity of entities" class="hover:bg-gray-200"> + <th> + <mat-checkbox + [checked]="entity.selected" + (change)="selectDeselectEntity(entity)" + > + </mat-checkbox> + </th> + <td>{{ entity.username }}</td> + <ng-container *ngIf="{filterName}"> + <td>{{ entity[filterName] }}</td> + </ng-container> + </tr> + </tbody> +</table> diff --git a/Frontend Angular 4/src/app/shared/components/checkbox-select/checkbox-select.component.ts b/Frontend Angular 4/src/app/shared/components/checkbox-select/checkbox-select.component.ts new file mode 100644 index 0000000..4aff606 --- /dev/null +++ b/Frontend Angular 4/src/app/shared/components/checkbox-select/checkbox-select.component.ts @@ -0,0 +1,64 @@ +import { + Component, + ChangeDetectionStrategy, + Input, + Output, + EventEmitter, + OnInit, + ChangeDetectorRef, + OnChanges, + SimpleChanges, +} from "@angular/core"; + +import { selectableUser } from "../../objects/archivo-types"; + +import { NgbActiveModal } from "@ng-bootstrap/ng-bootstrap"; + +@Component({ + selector: "app-checkbox-select", + templateUrl: "./checkbox-select.component.html", + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class CheckboxSelect implements OnInit, OnChanges { + @Input() entitiesName: string; + @Input() entities: selectableUser[] = []; + @Input() filterName: string; + @Output() entitiesChanged = new EventEmitter<selectableUser[]>(); + + constructor( + public modal: NgbActiveModal, + private changeDetectorRef: ChangeDetectorRef + ) {} + + ngOnInit(): void {} + + ngOnChanges(changes: SimpleChanges): void { + // if (!!changes.users) { + // this.users.forEach((user) => { + // this.usersObject[user.id] = user.role; + // }); + // } + } + + selectDeselectEntity(entity: selectableUser): void { + entity.selected = !entity.selected; + this.entitiesChange(this.entities); + } + + entitiesChange(entities: selectableUser[]): void { + this.entities = entities; + this.entitiesChanged.emit(this.entities); + this.changeDetectorRef.detectChanges(); + } + + allEntitiesSelected() { + return this.entities.every((entity) => entity.selected); + } + + onCheckboxChange(checked: boolean): void { + this.entities.forEach((entity) => { + entity.selected = checked; + }); + this.entitiesChange(this.entities); + } +} diff --git a/Frontend Angular 4/src/app/shared/components/index.ts b/Frontend Angular 4/src/app/shared/components/index.ts index ebf3490..8822948 100755 --- a/Frontend Angular 4/src/app/shared/components/index.ts +++ b/Frontend Angular 4/src/app/shared/components/index.ts @@ -14,3 +14,5 @@ export * from "./action-confirmation-modal/action-confirmation-modal.component"; export * from "./destroy-group-modal/destroy-group-modal.component"; export * from "./request-feedback-modal/request-feedback-modal.component"; export * from "./return-feedback-modal/return-feedback-modal.component"; +export * from "./assign-file-modal/assign-file-modal.component"; +export * from "./checkbox-select/checkbox-select.component"; diff --git a/Frontend Angular 4/src/app/shared/config.ts b/Frontend Angular 4/src/app/shared/config.ts index 953ce88..01e774f 100755 --- a/Frontend Angular 4/src/app/shared/config.ts +++ b/Frontend Angular 4/src/app/shared/config.ts @@ -72,3 +72,5 @@ export const CREATE_GROUP_FILE_FEEDBACK_REQUEST = SERVER + "/api/v2/groups/:group_id/feedback_requests"; export const GET_GROUP_USER_ROLE = SERVER + "/api/v2/groups/:group_id/users/:user_id/role"; +export const ASSIGN_FILE = + SERVER + "/api/v2/groups/:group_id/files/:file_id/assignment"; diff --git a/Frontend Angular 4/src/app/shared/objects/archivo-types.ts b/Frontend Angular 4/src/app/shared/objects/archivo-types.ts index 111b577..e2dfc84 100755 --- a/Frontend Angular 4/src/app/shared/objects/archivo-types.ts +++ b/Frontend Angular 4/src/app/shared/objects/archivo-types.ts @@ -75,6 +75,10 @@ export class User { role?: string; } +export class selectableUser extends User { + selected: boolean; +} + export class Grupo { anio: number; grado: number; diff --git a/Frontend Angular 4/src/app/shared/services/group-file.service.ts b/Frontend Angular 4/src/app/shared/services/group-file.service.ts index 210aef5..e08db79 100644 --- a/Frontend Angular 4/src/app/shared/services/group-file.service.ts +++ b/Frontend Angular 4/src/app/shared/services/group-file.service.ts @@ -20,6 +20,7 @@ import { GET_GROUP_FILES, GET_GROUP_FILES_LISTS, GET_GROUP_FEEDBACK_REQUESTED_FILES, + ASSIGN_FILE, } from "../config"; @Injectable() @@ -242,6 +243,25 @@ export class GroupFileService { ); } + assignFile( + groupId: number, + fileId: number, + usersIds, + subgroupsIds + ): Observable<HttpResponse<Object>> { + return this.http.post<Object>( + `${ASSIGN_FILE.replace(/:group_id/g, String(groupId)).replace( + /:file_id/g, + String(fileId) + )}`, + { assignment: { user_ids: usersIds } }, + { + observe: "response", + headers: this.postHeaders(), + } + ); + } + /** * Handle HTTP error */ diff --git a/Frontend Angular 4/src/app/shared/services/group.service.ts b/Frontend Angular 4/src/app/shared/services/group.service.ts index bf3e8d8..8d42f40 100644 --- a/Frontend Angular 4/src/app/shared/services/group.service.ts +++ b/Frontend Angular 4/src/app/shared/services/group.service.ts @@ -254,6 +254,10 @@ export class GroupService { return this.userCanEditAnyGroupFile(role); } + userCanAssignFiles(role: string): boolean { + return this.userCanEditAnyGroupFile(role); + } + /** * Handle HTTP error */ diff --git a/Frontend Angular 4/src/app/shared/shared.module.ts b/Frontend Angular 4/src/app/shared/shared.module.ts index 8012c63..bf77a6d 100644 --- a/Frontend Angular 4/src/app/shared/shared.module.ts +++ b/Frontend Angular 4/src/app/shared/shared.module.ts @@ -6,6 +6,7 @@ import { CommonModule } from "@angular/common"; import { FormsModule } from "@angular/forms"; import { MatFormFieldModule } from "@angular/material/form-field"; import { MatChipsModule } from "@angular/material/chips"; +import { MatCheckboxModule } from "@angular/material/checkbox"; import { MatIconModule } from "@angular/material/icon"; import { NgbDropdownModule } from "@ng-bootstrap/ng-bootstrap"; import { @@ -22,6 +23,8 @@ import { ReturnFeedbackModal, SelectComponent, ShareFileModal, + AssignFileModal, + CheckboxSelect, } from "./components"; @NgModule({ imports: [ @@ -31,6 +34,7 @@ import { FormsModule, MatFormFieldModule, MatChipsModule, + MatCheckboxModule, MatIconModule, NgbDropdownModule, ], @@ -49,6 +53,8 @@ import { DestroyGroupModal, RequestFeedbackModal, ReturnFeedbackModal, + AssignFileModal, + CheckboxSelect, ], exports: [MainButtonComponent, CreateFileModal], }) -- GitLab From f4b1b2ed9f80c1d5393a9343042782b6129bdb38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rodr=C3=ADguez?= <diego.rodriguez@rootstrap.com> Date: Fri, 7 Feb 2025 10:25:36 -0300 Subject: [PATCH 5/9] Add translation to assign files --- Frontend Angular 4/src/app/layout/grupos/grupos.component.html | 2 +- Frontend Angular 4/src/assets/i18n/en.json | 3 ++- Frontend Angular 4/src/assets/i18n/es.json | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Frontend Angular 4/src/app/layout/grupos/grupos.component.html b/Frontend Angular 4/src/app/layout/grupos/grupos.component.html index 8c5625d..eb4a69e 100755 --- a/Frontend Angular 4/src/app/layout/grupos/grupos.component.html +++ b/Frontend Angular 4/src/app/layout/grupos/grupos.component.html @@ -327,7 +327,7 @@ *ngIf="canAssignFile()" ngbPopover="{{ 'i18n.action.assignFile' | translate | titleCase - }} {{ 'i18n.object.file' | translate | titleCase }}" + }} {{ archivoSeleccionado.nombre }}" placement="bottom" triggers="mouseenter:mouseleave" class="btn btn-sm btn-secondary pull-left mr-2" diff --git a/Frontend Angular 4/src/assets/i18n/en.json b/Frontend Angular 4/src/assets/i18n/en.json index a33b35e..4f83be6 100755 --- a/Frontend Angular 4/src/assets/i18n/en.json +++ b/Frontend Angular 4/src/assets/i18n/en.json @@ -35,7 +35,8 @@ "confirm": "confirm", "signup": "sign up", "requestFeedback": "Request feedback", - "returnFeedback": "Return feedback" + "returnFeedback": "Return feedback", + "assignFile": "Assign file" }, "links": { "register": "Create account", diff --git a/Frontend Angular 4/src/assets/i18n/es.json b/Frontend Angular 4/src/assets/i18n/es.json index e3737b6..e7f1086 100755 --- a/Frontend Angular 4/src/assets/i18n/es.json +++ b/Frontend Angular 4/src/assets/i18n/es.json @@ -35,7 +35,8 @@ "confirm": "confirmar", "signup": "registrarse", "requestFeedback": "Solicitar retroalimentación", - "returnFeedback": "Devolver retroalimentación" + "returnFeedback": "Devolver retroalimentación", + "assignFile": "Asignar archivo" }, "links": { "register": "Registrarse", -- GitLab From 95becc1aca360781b51bda75bf257dcf0ce33275 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rodr=C3=ADguez?= <diego.rodriguez@rootstrap.com> Date: Fri, 7 Feb 2025 10:40:02 -0300 Subject: [PATCH 6/9] Check checkbox when clicking on row --- .../checkbox-select/checkbox-select.component.html | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Frontend Angular 4/src/app/shared/components/checkbox-select/checkbox-select.component.html b/Frontend Angular 4/src/app/shared/components/checkbox-select/checkbox-select.component.html index 7b0dd18..80297d1 100644 --- a/Frontend Angular 4/src/app/shared/components/checkbox-select/checkbox-select.component.html +++ b/Frontend Angular 4/src/app/shared/components/checkbox-select/checkbox-select.component.html @@ -16,9 +16,14 @@ </tr> </thead> <tbody> - <tr *ngFor="let entity of entities" class="hover:bg-gray-200"> + <tr + *ngFor="let entity of entities" + (mouseup)="selectDeselectEntity(entity)" + class="hover:bg-gray-200" + > <th> <mat-checkbox + #inputCheckbox [checked]="entity.selected" (change)="selectDeselectEntity(entity)" > -- GitLab From d315164488caeaef607a0ecca7452e06a0df662b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rodr=C3=ADguez?= <diego.rodriguez@rootstrap.com> Date: Fri, 7 Feb 2025 11:08:04 -0300 Subject: [PATCH 7/9] Remove directories from groups --- .../src/app/layout/grupos/grupos.component.ts | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/Frontend Angular 4/src/app/layout/grupos/grupos.component.ts b/Frontend Angular 4/src/app/layout/grupos/grupos.component.ts index 25adddd..0b0fe03 100755 --- a/Frontend Angular 4/src/app/layout/grupos/grupos.component.ts +++ b/Frontend Angular 4/src/app/layout/grupos/grupos.component.ts @@ -425,6 +425,7 @@ export class GruposComponent { // this.loadFilesAndFolders(this.grupoSeleccionado.id, archivo.id); this.archivoSeleccionado = undefined; this.selectedFile = undefined; // Probando + this.archivoSeleccionado = archivo; // this.selectedUser = undefined; this.currentPath += `${archivo.nombre}/`; this.preview = ""; @@ -588,14 +589,30 @@ export class GruposComponent { } confirmFileDeletion() { + let fileIdToDelete = null; + let navBack = false; + if (this.archivoSeleccionado?.fileId) { + fileIdToDelete = this.archivoSeleccionado.fileId; + } else if (this.selectedFile?.fileId) { + fileIdToDelete = this.selectedFile.fileId; + } else if (this.archivoSeleccionado?.id) { + fileIdToDelete = this.archivoSeleccionado.id; + navBack = true; + } + this.groupFileService .deleteGroupFile( this.grupoSeleccionado.id, - this.archivoSeleccionado?.fileId || this.selectedFile?.fileId + this.archivoSeleccionado?.fileId || + this.selectedFile?.fileId || + this.archivoSeleccionado?.id ) .subscribe( (_) => { this.modalRemoveFileOpened = false; + if (navBack) { + this.navBack(); + } this.loadFilesAndFolders( this.grupoSeleccionado.id, this.directorioActual.id -- GitLab From 3f76250ef4180f3d546715d0a73126ea64af1228 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rodr=C3=ADguez?= <diego.rodriguez@rootstrap.com> Date: Tue, 11 Feb 2025 14:36:30 -0300 Subject: [PATCH 8/9] Fix condition so group moderators can edit all group files --- .../src/app/layout/matefun/matefun.component.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Frontend Angular 4/src/app/layout/matefun/matefun.component.ts b/Frontend Angular 4/src/app/layout/matefun/matefun.component.ts index f8a3199..dde9109 100755 --- a/Frontend Angular 4/src/app/layout/matefun/matefun.component.ts +++ b/Frontend Angular 4/src/app/layout/matefun/matefun.component.ts @@ -923,6 +923,7 @@ export class MateFunComponent implements OnInit, OnChanges { initializeEditor() { let readOnly = false; + if (!!this.archivo.users) { if (this.archivo.users.length > 0) { const id = JSON.parse(localStorage.getItem("currentUser"))["id"]; @@ -934,6 +935,7 @@ export class MateFunComponent implements OnInit, OnChanges { if (!!user) { readOnly = user.role === "viewer"; } + if ((!user || readOnly == true) && !!this.groupId) { const id = JSON.parse(localStorage.getItem("currentUser"))["id"]; this.groupService.getUserRole(this.groupId, id).subscribe((data) => { @@ -941,7 +943,7 @@ export class MateFunComponent implements OnInit, OnChanges { this.editor.setOption( "readOnly", - this.groupService.userCanEditAnyGroupFile( + !this.groupService.userCanEditAnyGroupFile( this.currentUserGroupRole ) ); -- GitLab From 6e939451395eb9d38bb17728388c34b7af9889bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rodr=C3=ADguez?= <diego.rodriguez@rootstrap.com> Date: Tue, 11 Feb 2025 14:45:27 -0300 Subject: [PATCH 9/9] Removed many comments --- .../src/app/layout/grupos/grupos.component.ts | 18 +- .../app/layout/matefun/matefun.component.ts | 13 -- .../manage-group-users-modal.component.html | 38 ---- .../manage-group-users-modal.component.ts | 5 - .../request-feedback-modal.component.ts | 2 - .../return-feedback-modal.component.ts | 2 - .../shared/services/group-document.service.ts | 212 ------------------ .../src/app/shared/services/group.service.ts | 12 - 8 files changed, 1 insertion(+), 301 deletions(-) diff --git a/Frontend Angular 4/src/app/layout/grupos/grupos.component.ts b/Frontend Angular 4/src/app/layout/grupos/grupos.component.ts index 0b0fe03..45e72b6 100755 --- a/Frontend Angular 4/src/app/layout/grupos/grupos.component.ts +++ b/Frontend Angular 4/src/app/layout/grupos/grupos.component.ts @@ -402,31 +402,15 @@ export class GruposComponent { this.directorioActual.id ); } - // let selectedFile = this.selectedUserDirectorioActual?.archivos?.find( - // (archivo) => archivo.documentId === this.selectedFile.id - // ); - // if (!!selectedFile) { - // this.seleccionarUser(this.selectedUser); - // selectedFile.feedbackRequested = false; - // } else { - // selectedFile = this.directorioActual.archivos.find( - // (archivo) => archivo.documentId === this.selectedFile.id - // ); - // if (!!selectedFile) { - // selectedFile.feedbackRequested = false; - // } - // } } seleccionarArchivo(archivo) { this.selectedFile = archivo; if (archivo.directorio) { this.directorioActual = archivo; - // this.loadFilesAndFolders(this.grupoSeleccionado.id, archivo.id); this.archivoSeleccionado = undefined; this.selectedFile = undefined; // Probando this.archivoSeleccionado = archivo; - // this.selectedUser = undefined; this.currentPath += `${archivo.nombre}/`; this.preview = ""; } else { @@ -460,7 +444,7 @@ export class GruposComponent { archivo.feedbackRequested = this.selectedFile.feedbackRequested; } this.archivoSeleccionado = archivo; - this.selectedFile = archivo; //Aquí el problema, se necesita para los que pidieron feedback, pero jode para los archivos propios + this.selectedFile = archivo; this.preview = !!archivo?.contenido ? archivo.contenido : ""; } diff --git a/Frontend Angular 4/src/app/layout/matefun/matefun.component.ts b/Frontend Angular 4/src/app/layout/matefun/matefun.component.ts index dde9109..1906dd1 100755 --- a/Frontend Angular 4/src/app/layout/matefun/matefun.component.ts +++ b/Frontend Angular 4/src/app/layout/matefun/matefun.component.ts @@ -742,19 +742,6 @@ export class MateFunComponent implements OnInit, OnChanges { } else { this.configCodeMirror.mode.name = "matefun-ES"; } - - // this.fileService.getFiles().subscribe((files) => { - // let matefun_files = files["files"]; - // matefun_files = this.fileService.completeFiles(matefun_files); - // this.sessionService.setUserFiles(matefun_files); - // let fileArchivos = this.fileService.fileToArchivo(matefun_files); - - // this.archivosTree = fileArchivos; - // if (!!this.editor) { - // (this.editor as any).options.files = this.archivosTree; - // } - // this.sessionService.setArchivosTree(this.archivosTree); - // }); } importFilesData(data) { diff --git a/Frontend Angular 4/src/app/shared/components/manage-group-users-modal/manage-group-users-modal.component.html b/Frontend Angular 4/src/app/shared/components/manage-group-users-modal/manage-group-users-modal.component.html index 7608fa9..891497b 100644 --- a/Frontend Angular 4/src/app/shared/components/manage-group-users-modal/manage-group-users-modal.component.html +++ b/Frontend Angular 4/src/app/shared/components/manage-group-users-modal/manage-group-users-modal.component.html @@ -9,41 +9,3 @@ [requiredRole]="'Owner'" [save]="save" ></app-add-users-modal> - -<!-- <div class="modal-header"> - <span class="modal-title font-bold" id="modal-title">{{ modalTitle }}</span> - <button - aria-label="Cerrar diálogo" - (click)="modal.dismiss('Close share file modal')" - part="close-button" - class="close-button fa fa-close" - type="button" - ></button> -</div> -<div class="modal-body"> - <div class="flex flex-col justify-between w-full"> - <label for="new-users">New Users</label> - <div class="flex justify-between w-full items-center"> - <app-chip-input - [(chips)]="chips" - [placeholder]="'Add a new user'" - [id]="'new-users'" - class="w-full" - ></app-chip-input> - <app-select - [options]="newUsersPermissionsEnum" - [(selectedOption)]="newUsersPermission" - ></app-select> - </div> - </div> - <div class="flex justify-end"> - <button - (click)="confirmFileCreation(documentTitle, currentFolder.id)" - [disabled]="!documentTitle || !currentFolder" - class="btn btn-primary" - slot="primary" - > - Guardar - </button> - </div> -</div> --> diff --git a/Frontend Angular 4/src/app/shared/components/manage-group-users-modal/manage-group-users-modal.component.ts b/Frontend Angular 4/src/app/shared/components/manage-group-users-modal/manage-group-users-modal.component.ts index 22303ff..a8dfef4 100644 --- a/Frontend Angular 4/src/app/shared/components/manage-group-users-modal/manage-group-users-modal.component.ts +++ b/Frontend Angular 4/src/app/shared/components/manage-group-users-modal/manage-group-users-modal.component.ts @@ -37,11 +37,6 @@ export class ManageGroupUsersModal implements OnInit { */ @Input() confirmGroupUpdated: () => void; - // Hacer get del grupo al inicializar el modal - // Capaz mostrar un loader mientras ocurre - // Con eso mostrar los usuarios que tienen acceso al grupo y sus permisos - // Y usar el group_id para hacer el share - chips: string[] = []; constructor( diff --git a/Frontend Angular 4/src/app/shared/components/request-feedback-modal/request-feedback-modal.component.ts b/Frontend Angular 4/src/app/shared/components/request-feedback-modal/request-feedback-modal.component.ts index ee43f09..968d5e1 100644 --- a/Frontend Angular 4/src/app/shared/components/request-feedback-modal/request-feedback-modal.component.ts +++ b/Frontend Angular 4/src/app/shared/components/request-feedback-modal/request-feedback-modal.component.ts @@ -15,8 +15,6 @@ import { GroupFileService } from "../../services/group-file.service"; changeDetection: ChangeDetectionStrategy.OnPush, }) export class RequestFeedbackModal implements OnInit { - // TODO: Hacer todo este modal basándome en lo que ya está que viene del DestroyGroupModal, - // y luego terminar de hacer la llamada y todo en el GroupComponent @Input() fileName: string; @Input() groupName: string | null; @Input() groupId: number; diff --git a/Frontend Angular 4/src/app/shared/components/return-feedback-modal/return-feedback-modal.component.ts b/Frontend Angular 4/src/app/shared/components/return-feedback-modal/return-feedback-modal.component.ts index 550dbe3..422ebad 100644 --- a/Frontend Angular 4/src/app/shared/components/return-feedback-modal/return-feedback-modal.component.ts +++ b/Frontend Angular 4/src/app/shared/components/return-feedback-modal/return-feedback-modal.component.ts @@ -15,8 +15,6 @@ import { GroupFileService } from "../../services/group-file.service"; changeDetection: ChangeDetectionStrategy.OnPush, }) export class ReturnFeedbackModal implements OnInit { - // TODO: Hacer todo este modal basándome en lo que ya está que viene del DestroyGroupModal, - // y luego terminar de hacer la llamada y todo en el GroupComponent @Input() fileName: string; @Input() groupName: string | null; @Input() groupId: number; diff --git a/Frontend Angular 4/src/app/shared/services/group-document.service.ts b/Frontend Angular 4/src/app/shared/services/group-document.service.ts index b1df8dd..a76638c 100644 --- a/Frontend Angular 4/src/app/shared/services/group-document.service.ts +++ b/Frontend Angular 4/src/app/shared/services/group-document.service.ts @@ -54,18 +54,6 @@ export class GroupDocumentService { }); } - // getDocuments(ids: number[]): Observable<{ documents: MDocument[] }> { - // const params = new HttpParams({ - // fromObject: { "document_ids[]": ids }, - // }); - // return this.http - // .get<{ documents: MDocument[] }>(GET_DOCUMENTS, { - // headers: this.getHeaders(), - // params: params, - // }) - // .pipe(catchError(this.handleError)); - // } - getGroupDocument( groupId: number, id: number, @@ -89,206 +77,6 @@ export class GroupDocumentService { .pipe(catchError(this.handleError)); } - // documentToArchivo(document: MDocument): Archivo { - // return { - // id: document.id, - // documentId: document.id, - // nombre: document.title, - // contenido: document.text_doc, - // fechaCreacion: document.created_at, - // cedulaCreador: document.users[0]?.email, - // editable: true, - // padreId: document.parent_id ? document.parent_id : -1, - // fileId: document.file_id, - // archivoOrigenId: null, - // archivos: [], - // directorio: false, - // estado: "activo", - // eliminado: false, - // evaluacion: null, - // users: document.users, - // role: document.role, - // }; - // } - - // archivoToDocument(archivo: Archivo): MDocument { - // return { - // id: archivo.id, - // title: archivo.nombre, - // text_doc: archivo.contenido, - // created_at: archivo.fechaCreacion, - // parent_id: archivo.padreId, - // role: archivo.role, - // users: [], - // }; - // } - - // createDocument( - // document: MDocument, - // groupId?: number - // ): Observable<HttpResponse<MDocument>> { - // const params = { ...document }; - // if (groupId) { - // params["group_id"] = groupId; - // } - // return this.http.post<MDocument>( - // CREATE_DOCUMENT, - // { document: params }, - // { - // observe: "response", - // headers: this.postHeaders(), - // } - // ); - // } - - // // Legacy method, need to check if it's still used - // getArchivos(cedula: string): Observable<Archivo[]> { - // let headers = this.getHeaders(); - // let params: HttpParams = new HttpParams(); - // params = params.set("cedula", cedula); - // let httpOptions = { headers: headers, params: params }; - - // return this.http - // .get<Archivo[]>(SERVER + "/servicios/archivo", httpOptions) - // .pipe(catchError(this.handleError)); - // } - - // // Legacy method, need to check if it's still used - // getArchivosCompartidosAlumno(cedula: string): Observable<Archivo[]> { - // let headers = this.getHeaders(); - // let params: HttpParams = new HttpParams(); - // params = params.set("cedula", cedula); - // params = params.set("compartidos", "true"); - // let httpOptions = { headers: headers, params: params }; - - // return this.http - // .get<Archivo[]>(SERVER + "/servicios/archivo", httpOptions) - // .pipe(catchError(this.handleError)); - // } - - // createFile( - // file_title, - // file_binary_doc_text - // ): Observable<HttpResponse<Object>> { - // return this.http.post<Object>( - // CREATE_DOCUMENT, - // { - // document: { title: file_title, text_doc: file_binary_doc_text }, - // }, - // { - // observe: "response", - // headers: this.postHeaders(), - // } - // ); - // } - - // updateDocument( - // file_id, - // file_title, - // save_document_content = false - // ): Observable<HttpResponse<Object>> { - // return this.http.patch<Object>( - // `${UPDATE_DOCUMENT}/${file_id}`, - // { - // document: { - // title: file_title, - // save_document_content: save_document_content, - // }, - // }, - // { - // observe: "response", - // headers: this.postHeaders(), - // } - // ); - // } - - // destroyAuxDocument(): Observable<HttpResponse<Object>> { - // return this.http.delete<Object>(DESTROY_AUX_DOCUMENT, { - // observe: "response", - // headers: this.postHeaders(), - // }); - // } - - // // Legacy method, need to check if it's still used - // eliminarArchivo(archivoId): Observable<Response> { - // let headers = this.getHeaders(); - // let httpOptions = { headers: headers }; - - // return this.http - // .delete<Response>(SERVER + "/servicios/archivo/" + archivoId, httpOptions) - // .pipe(catchError(this.handleError)); - // } - - // // Legacy method, need to check if it's still used - // getCopiaArchivoCompartidoGrupo(cedula, archivoId): Observable<Archivo> { - // let headers = this.getHeaders(); - // let params: HttpParams = new HttpParams(); - // params = params.set("cedula", cedula); - // let httpOptions = { headers: headers, params: params }; - - // return this.http - // .get<Archivo>( - // SERVER + "/servicios/archivo/compartido/" + archivoId, - // httpOptions - // ) - // .pipe(catchError(this.handleError)); - // } - - // // Legacy method, need to check if it's still used - // compartirArchivoGrupo(grupo, archivoId): Observable<Archivo> { - // let headers = this.getHeaders(); - // let httpOptions = { headers: headers }; - // var archId = { - // id: archivoId, - // }; - // return this.http - // .post<Archivo>( - // SERVER + - // "/servicios/grupo/" + - // grupo.liceoId + - // "/" + - // grupo.anio + - // "/" + - // grupo.grado + - // "/" + - // grupo.grupo + - // "/archivo", - // archId, - // httpOptions - // ) - // .pipe(catchError(this.handleError)); - // } - - // // Legacy method, need to check if it's still used - // calificarArchivo(archivoId, estado, evaluacion): Observable<Evaluacion> { - // let headers = this.getHeaders(); - // let httpOptions = { headers: headers }; - // return this.http - // .post<Evaluacion>( - // SERVER + - // "/servicios/archivo/" + - // archivoId + - // "/" + - // estado + - // "/evaluacion", - // evaluacion, - // httpOptions - // ) - // .pipe(catchError(this.handleError)); - // } - - // // Legacy method, need to check if it's still used - // getGrupos(cedula: string): Observable<Group[]> { - // let headers = this.getHeaders(); - // let params: HttpParams = new HttpParams(); - // params = params.set("cedula", cedula); - // let httpOptions = { headers: headers, params: params }; - - // return this.http - // .get<Group[]>(SERVER + "/servicios/grupo", httpOptions) - // .pipe(catchError(this.handleError)); - // } - /** * Handle HTTP error */ diff --git a/Frontend Angular 4/src/app/shared/services/group.service.ts b/Frontend Angular 4/src/app/shared/services/group.service.ts index 8d42f40..94080d5 100644 --- a/Frontend Angular 4/src/app/shared/services/group.service.ts +++ b/Frontend Angular 4/src/app/shared/services/group.service.ts @@ -72,18 +72,6 @@ export class GroupService { .pipe(catchError(this.handleError)); } - // getFilesList(options = {}): Observable<{ files: MFile[] }> { - // const siblingsOfId = options["siblingsOfId"]; - // return this.http - // .get<{ files: MFile[] }>(GET_FILES_LISTS, { - // headers: this.getHeaders(), - // params: siblingsOfId - // ? new HttpParams().set("siblings_of_id", siblingsOfId) - // : null, - // }) - // .pipe(catchError(this.handleError)); - // } - fileToArchivo(file: MFile, parent: Archivo = null): Archivo { let archivo = new Archivo(); archivo; -- GitLab