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