Просмотр исходного кода

actualizado dashboards para el botón

Mario Martínez Hernández 1 месяц назад
Родитель
Сommit
22e61b84f6

+ 32 - 0
src/main/java/es/uv/saic/dto/ProcedureRequestDTO.java

@@ -0,0 +1,32 @@
+package es.uv.saic.dto;
+
+import java.util.List;
+
+public class ProcedureRequestDTO {
+    private ProcesDTO proces;
+    private List<TascaDTO> tasques;
+
+    public ProcedureRequestDTO() {}
+
+    public ProcedureRequestDTO(ProcesDTO proces, List<TascaDTO> tasques) {
+        this.proces = proces;
+        this.tasques = tasques;
+    }
+
+    public ProcesDTO getProces() {
+        return proces;
+    }
+
+    public void setProces(ProcesDTO proces) {
+        this.proces = proces;
+    }
+
+    public List<TascaDTO> getTasques() {
+        return tasques;
+    }
+
+    public void setTasques(List<TascaDTO> tasques) {
+        this.tasques = tasques;
+    }
+    
+}

+ 10 - 1
src/main/java/es/uv/saic/dto/TascaDTO.java

@@ -24,6 +24,7 @@ public class TascaDTO {
     private Integer idRol;
     private String nomRol;
     private Boolean informe;
+    private Integer idPlantilla;
 
     public TascaDTO() {
     }
@@ -31,7 +32,7 @@ public class TascaDTO {
     public TascaDTO(Integer idTasca, String codiEvidencia, String dataLim, String descripcioCas, String descripcioVal,
             Integer idTascaSeg, Integer idTascaSeg2, Integer idTascap, Integer idTipus, String nomTipus,
             String nomEvidenciaCas, String nomEvidenciaVal, String titolCas, String titolVal, String opcions,
-            Integer idRol, String nomRol, Boolean informe) {
+            Integer idRol, String nomRol, Boolean informe, Integer idPlantilla) {
         this.idTasca = idTasca;
         this.codiEvidencia = codiEvidencia;
         this.dataLim = dataLim;
@@ -50,6 +51,7 @@ public class TascaDTO {
         this.idRol = idRol;
         this.nomRol = nomRol;
         this.informe = informe;
+        this.idPlantilla = idPlantilla;
     }
 
     public TascaDTO(Tasca tasca) {
@@ -226,4 +228,11 @@ public class TascaDTO {
         this.informe = informe;
     }
     
+    public Integer getIdPlantilla() {
+        return idPlantilla;
+    }
+
+    public void setIdPlantilla(Integer idPlantilla) {
+        this.idPlantilla = idPlantilla; 
+    }
 }

+ 10 - 21
src/main/java/es/uv/saic/feign/AdminClient.java

@@ -11,10 +11,15 @@ import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
 
 import es.uv.saic.domain.Acreditacio;
 import es.uv.saic.domain.AcreditacioTransfer;
+import es.uv.saic.dto.ProcedureRequestDTO;
+import es.uv.saic.dto.ProcesDTO;
+import es.uv.saic.dto.TascaDTO;
 
 @FeignClient(name = "saic-core-service", contextId = "admin-controller")
 public interface AdminClient {
@@ -51,7 +56,7 @@ public interface AdminClient {
     void sendMails( @RequestParam Integer idRol, @RequestParam("centres[]") List<Integer> centres,
 			@RequestParam String subject, @RequestParam String body);
 
-    @PutMapping("/admin/procedures")
+    @PutMapping("/admin/procedure")
     void newProcedure(@RequestParam Map<String,String> params, 
 			@RequestParam(name="idTascap[]", required=false) List<String> idTascap, 
 			@RequestParam(name="dataLimit[]", required=false) List<String> dataLimit, 
@@ -70,34 +75,18 @@ public interface AdminClient {
 			@RequestParam(name="idPlantilla[]", required=false) List<String> idPlantilla,
 			@RequestParam(name="informe[]", required=false) List<Boolean> informe);
     
-    @PostMapping("/admin/procedures")
-    void editProcedure(@RequestParam Map<String,String> params, 
-			@RequestParam(name="idTascap[]", required=false) List<String> idTascap, 
-			@RequestParam(name="dataLimit[]", required=false) List<String> dataLimit, 
-			@RequestParam(name="tipus[]", required=false) List<String> tipus,
-			@RequestParam(name="codiEvidencia[]", required=false) List<String> codiEvidencia, 
-			@RequestParam(name="idTascaSeg[]", required=false) List<String> idTascaSeg, 
-			@RequestParam(name="idTascaSeg2[]", required=false) List<String> idTascaSeg2,
-			@RequestParam(name="opcions[]", required=false) List<String> opcions,
-			@RequestParam(name="idRol[]", required=false) List<String> idRol, 
-			@RequestParam(name="titolCas[]", required=false) List<String> titolCas, 
-			@RequestParam(name="titolVal[]", required=false) List<String> titolVal,
-			@RequestParam(name="descripcioCas[]", required=false) List<String> descripcioCas, 
-			@RequestParam(name="descripcioVal[]", required=false) List<String> descripcioVal,
-			@RequestParam(name="nomEvidenciaCas[]", required=false) List<String> nomEvidenciaCas, 
-			@RequestParam(name="nomEvidenciaVal[]", required=false) List<String> nomEvidenciaVal,
-			@RequestParam(name="idPlantilla[]", required=false) List<String> idPlantilla,
-			@RequestParam(name="informe[]", required=false) List<Boolean> informe);
+	@PostMapping("/admin/procedure")
+	public void editProcedure(@RequestBody ProcedureRequestDTO resquest);
 
     @DeleteMapping("/admin/procedures")
     void removeProcedure(@RequestParam("idProces") Integer idProces);    
     
     @DeleteMapping("/admin/userrole")
-    HashMap<String, Object> removeUserrole(@RequestParam("idRol") Integer idRol, @RequestParam("usuari") String usuari, 
+    boolean removeUserrole(@RequestParam("idRol") Integer idRol, @RequestParam("usuari") String usuari, 
 		@RequestParam("lugar") Integer lugar, @RequestParam("tlugar") String tlugar);
     
     @PutMapping("/admin/userrole")
-	HashMap<String, Object> newUserrole(@RequestParam("idRol") Integer idRol, @RequestParam(name="usuari", required=false) String usuari, @RequestParam("centre") Integer idCentre, 
+	boolean newUserrole(@RequestParam("idRol") Integer idRol, @RequestParam(name="usuari", required=false) String usuari, @RequestParam("centre") Integer idCentre, 
 			@RequestParam(name="titulacio", required=false) Integer idTitulacio, @RequestParam Map<String,String> params);
 	
 	@GetMapping("/admin/templates")

+ 9 - 9
src/main/java/es/uv/saic/feign/PlantillaClient.java

@@ -17,32 +17,32 @@ import es.uv.saic.dto.TemplateDataDTO;
 public interface PlantillaClient {
 
     //Se usa
-    @GetMapping("/plantillas")
+    @GetMapping("/plantilla")
     List<Plantilla> findAll();
 
     //Se usa
-    @GetMapping("/plantillas/{id}")
+    @GetMapping("/plantilla/{id}")
     Plantilla findByID(@PathVariable("id") Integer id);
 
-    @GetMapping("/plantillas/{versio}/{codi}/{ambit}")
+    @GetMapping("/plantilla/{versio}/{codi}/{ambit}")
     Plantilla findByVersioCodiAmbit(@PathVariable("versio") Integer versio, @PathVariable("codi") String codi, @PathVariable("ambit") String ambit);
 
-    @PostMapping("/plantillas")
+    @PostMapping("/plantilla")
     String save(@RequestBody Plantilla plantilla);
 
-    @DeleteMapping("/plantillas")
+    @DeleteMapping("/plantilla")
     String delete(@RequestBody Plantilla plantilla);
 
-    @GetMapping("/plantillas/used/{id}")
+    @GetMapping("/plantilla/used/{id}")
     Boolean isUsed(@PathVariable("id") Integer id);
 
-    @PostMapping("/plantillas/addTemplateData")
+    @PostMapping("/plantilla/addTemplateData")
     String addTemplateData(@RequestBody TemplateDataDTO td);
 
     //Se usa
-    @PostMapping("/plantillas/addTemplateData2")
+    @PostMapping("/plantilla/addTemplateData2")
     String addTemplateData2(@RequestBody TemplateDataDTO td);
 
-    @PostMapping("/plantillas/savePDF")
+    @PostMapping("/plantilla/savePDF")
     String savePDF(@RequestBody PdfDTO pdf);
 }

+ 21 - 38
src/main/java/es/uv/saic/web/AdminController.java

@@ -8,8 +8,6 @@ import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import jakarta.servlet.http.HttpServletResponse;
-import jakarta.servlet.http.HttpSession;
 
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.annotation.Secured;
@@ -19,6 +17,7 @@ import org.springframework.ui.Model;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.ResponseBody;
 
@@ -28,6 +27,7 @@ import es.uv.saic.domain.DummyDataTransfer;
 import es.uv.saic.domain.Plantilla;
 import es.uv.saic.domain.Usuari;
 import es.uv.saic.dto.OrganDTO;
+import es.uv.saic.dto.ProcedureRequestDTO;
 import es.uv.saic.dto.ProcesDTO;
 import es.uv.saic.dto.RolDTO;
 import es.uv.saic.dto.TemplateDataDTO;
@@ -37,6 +37,8 @@ import es.uv.saic.feign.OrganClient;
 import es.uv.saic.feign.PlantillaClient;
 import es.uv.saic.feign.ProceduresClient;
 import es.uv.saic.feign.UsuariClient;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.servlet.http.HttpSession;
 
 @Controller
 public class AdminController {
@@ -256,33 +258,15 @@ public class AdminController {
 		
 		return "adminProcedures";
 	}
-	
-	// POST para editar un procedimiento ya existente
+
 	@PostMapping("/admin/procedures/edit")
 	@Secured({"ROLE_ADMIN"})
-	public String editProcedure(Model model, Authentication auth, HttpServletResponse response, @RequestParam Map<String,String> params, 
-			@RequestParam(name="idTascap[]", required=false) List<String> idTascap, 
-			@RequestParam(name="dataLimit[]", required=false) List<String> dataLimit, 
-			@RequestParam(name="tipus[]", required=false) List<String> tipus,
-			@RequestParam(name="codiEvidencia[]", required=false) List<String> codiEvidencia, 
-			@RequestParam(name="idTascaSeg[]", required=false) List<String> idTascaSeg, 
-			@RequestParam(name="idTascaSeg2[]", required=false) List<String> idTascaSeg2,
-			@RequestParam(name="opcions[]", required=false) List<String> opcions,
-			@RequestParam(name="idRol[]", required=false) List<String> idRol, 
-			@RequestParam(name="titolCas[]", required=false) List<String> titolCas, 
-			@RequestParam(name="titolVal[]", required=false) List<String> titolVal,
-			@RequestParam(name="descripcioCas[]", required=false) List<String> descripcioCas, 
-			@RequestParam(name="descripcioVal[]", required=false) List<String> descripcioVal,
-			@RequestParam(name="nomEvidenciaCas[]", required=false) List<String> nomEvidenciaCas, 
-			@RequestParam(name="nomEvidenciaVal[]", required=false) List<String> nomEvidenciaVal,
-			@RequestParam(name="idPlantilla[]", required=false) List<String> idPlantilla,
-			@RequestParam(name="informe[]", required=false) List<Boolean> informe) 
-					throws NumberFormatException, ParseException {
-
-		ac.editProcedure(params, idTascap, dataLimit, tipus, codiEvidencia, idTascaSeg, idTascaSeg2, opcions, idRol, titolCas, titolVal, descripcioCas, descripcioVal, nomEvidenciaCas, nomEvidenciaVal, idPlantilla, informe);
-
-		this.loadProceduresData(model);
+	public String editProcedure(Model model, @RequestBody ProcedureRequestDTO request) {
+		// Extraemos los datos del wrapper
+		ac.editProcedure(request);
 		
+		this.loadProceduresData(model);
+
 		return "adminProcedures";
 	}
 	
@@ -300,28 +284,22 @@ public class AdminController {
 	// POST que le eliminar un usuario concreto del sitema
 	@PostMapping("/admin/userrole/remove")
 	@Secured({"ROLE_ADMIN", "ROLE_MANAGER"})
-	public void removeUserrole(Model model, Authentication auth, HttpServletResponse response, HttpSession session, @RequestParam("idRol") Integer idRol, 
+	@ResponseBody
+	public boolean removeUserrole(Model model, Authentication auth, HttpServletResponse response, HttpSession session, @RequestParam("idRol") Integer idRol, 
 			@RequestParam("usuari") String usuari, @RequestParam("lugar") Integer lugar, @RequestParam("tlugar") String tlugar) throws IOException{
 
-		HashMap<String, Object> map = ac.removeUserrole(idRol, usuari, lugar, tlugar);
-
-	    response.sendRedirect("/dashboard");
+		return ac.removeUserrole(idRol, usuari, lugar, tlugar);
 	}
 	
 	// POST para añadir un nuevo usuario al sistema
 	@PostMapping("/admin/userrole/new")
 	@Secured({"ROLE_ADMIN", "ROLE_MANAGER"})
-	public void newUserrole(Model model, Authentication auth, HttpServletResponse response, HttpSession session, @RequestParam("idRol") Integer idRol, 
+	@ResponseBody
+	public boolean newUserrole(Model model, Authentication auth, HttpServletResponse response, HttpSession session, @RequestParam("idRol") Integer idRol, 
 			@RequestParam(name="usuari", required=false) String usuari, @RequestParam("centre") Integer idCentre, 
 			@RequestParam(name="titulacio", required=false) Integer idTitulacio, @RequestParam Map<String,String> params) throws IOException{
 
-		HashMap<String, Object> map = ac.newUserrole(idRol, usuari, idCentre, idTitulacio, params);		
-
-		session.setAttribute("respCentres", (List<Integer>) map.get("respCentres"));
-		session.setAttribute("respTitulacions", (List<Integer>) map.get("respTitulacions"));
-		session.setAttribute("roleExists", map.get("roleExists"));
-	
-	    response.sendRedirect("/dashboard");
+		return ac.newUserrole(idRol, usuari, idCentre, idTitulacio, params);
 	}
 	
 	//¿POSIBLE ELIMINACIÓN?
@@ -420,4 +398,9 @@ public class AdminController {
 		return ac.UpdateAcreditacio(tlugar, lugar, grupCurs, grupNum, cursImpla, dataAcred, dataRenov, dataSegui, dataVerif, recom, segui);
 	}
 	
+	@GetMapping("/admin/checkProcedureID")
+	@ResponseBody
+	public boolean checkProcedureID(@RequestParam String id) {
+		return pc.findProcesByID(Integer.parseInt(id)) != null;
+	}
 }

+ 112 - 11
src/main/resources/templates/adminProcedures.html

@@ -121,7 +121,12 @@
 
 		mermaid.initialize({ startOnLoad: false, theme: "default", flowchart: { diagramPadding: 5, useMaxWidth:false, htmlLabels:true, wrap: true, wrappingWidth: 750 } });
 		
-		$('#procedureForm').submit(function(e) {
+		// Intercept form submit, serialize to JSON and send via fetch.
+		$('#procedureForm').on('submit', function(e) {
+			e.preventDefault();
+			$('.hiddenFields').show();
+
+			// Normalize empty text inputs and textareas (same behavior as before)
 			$("input[type=text]").each(function() {
 				var element = $(this);
 				if (element.val() == "" || element.val() == " ") {
@@ -142,12 +147,96 @@
 				var element = $(this);
 				element.val(element.val().replaceAll(',', "[comma]"));
 			});
-		    return true;
-		});
-		
-		$('#procedureForm').on('submit', function() {
-			$('.hiddenFields').show();
-			return true;
+
+			// Build JSON payload
+			var payload = {
+				proces,
+				tasques: []
+			};
+
+			var proces = {
+				idProces: $('#idProces').val(),
+				nomProces: $('#nomProces').val(),
+				subTitol: $('#subTitol').val(),
+				ambit: $('#ambit').val(),
+				versio: $('#versio').val(),
+				cursActivacio: $('#cursActivacio').val(),
+				cursAvaluat: $('#cursAvaluat').val(),
+				titolCas: $('#titolCas').val(),
+				titolVal: $('#titolVal').val(),
+				descripcioCas: $('#descripcioCas').val(),
+				descripcioVal: $('#descripcioVal').val(),
+				comentaris: $('#comentaris').val(),
+			};
+
+			payload.proces = proces;
+
+			$('.card-task').each(function(i, d){
+				var $d = $(d);
+				var task = {
+					idTascap: $d.find("input[name='idTascap[]']").val(),
+					dataLimit: $d.find("input[name='dataLimit[]']").val(),
+					tipus: $d.find("select[name='tipus[]']").val(),
+					idRol: $d.find("select[name='idRol[]']").val(),
+					idTascaSeg: $d.find("input[name='idTascaSeg[]']").val(),
+					idTascaSeg2: $d.find("input[name='idTascaSeg2[]']").val(),
+					opcions: $d.find("input[name='opcions[]']").val(),
+					informe: $d.find("select[name='informe[]']").val() === 'true',
+					titolCas: $d.find("input[name='titolCas[]']").val(),
+					titolVal: $d.find("input[name='titolVal[]']").val(),
+					descripcioCas: $d.find("textarea[name='descripcioCas[]']").val(),
+					descripcioVal: $d.find("textarea[name='descripcioVal[]']").val(),
+					codiEvidencia: $d.find("input[name='codiEvidencia[]']").val(),
+					nomEvidenciaCas: $d.find("input[name='nomEvidenciaCas[]']").val(),
+					nomEvidenciaVal: $d.find("input[name='nomEvidenciaVal[]']").val(),
+					idPlantilla: $d.find("select[name='idPlantilla[]']").val()
+				};
+				payload.tasques.push(task);
+			});
+
+			payload.tasques.forEach(t => {
+				if (t.idTascap === ".") t.idTascap = null;
+				if (t.idTascaSeg === ".") t.idTascaSeg = null;
+				if (t.idTascaSeg2 === ".") t.idTascaSeg2 = null;
+			});
+
+			var url = $(this).attr('action') || '/admin/procedures';
+
+			var $submitButtons = $(this).find("button[type='submit'], input[type='submit']");
+			$submitButtons.prop('disabled', true);
+
+			const params = new URLSearchParams();
+			params.append('proces', JSON.stringify(payload.proces));
+			params.append('tasks', JSON.stringify(payload.tasques));
+
+			fetch(url, {
+				method: 'POST',
+				headers: {
+					'Content-Type': 'application/json'
+				},
+				body: JSON.stringify(payload)
+			})
+			.then(resp => {
+				if (resp.redirected) {
+					window.location = resp.url;
+					return;
+				}
+				return resp.text();
+			})
+			.then(text => {
+				if (text) {
+					$('#formFields').empty();
+					$('#formFields').html(text);
+					$('#formFields').find('.selectpicker').selectpicker('refresh');
+					window.location.reload();
+				}
+				$submitButtons.prop('disabled', false);
+			})
+			.catch(err => {
+				console.error(err);
+				alert('Error en el servidor');
+				$submitButtons.prop('disabled', false);
+			});
 		});
 	});
 	
@@ -158,7 +247,7 @@
 			$('#formFields').show();
 		});
 		$('#formFields').show();
-		$('#procedureForm').attr('action', '/admin/procedures');
+		$('#procedureForm').attr('action', '/admin/procedures/new');
 	}
 	
 	function formDuplicate(){
@@ -173,7 +262,7 @@
 			    clearBtn: true
 			});
 			$('#formFields').show();
-			$('#procedureForm').attr('action', '/admin/procedures');
+			$('#procedureForm').attr('action', '/admin/procedures/new');
 		});
 	}
 	
@@ -182,7 +271,7 @@
 			$('#formFields').html(rawData);
 			$('.selectpicker').selectpicker('refresh');
 			$('#formFields').show();
-			$('#procedureForm').attr('action', '/admin/procedures');
+			$('#procedureForm').attr('action', '/admin/procedures/remove');
 		});
 	}
 		
@@ -191,7 +280,7 @@
 			$('#formFields').html(rawData);
 			$('.selectpicker').selectpicker('refresh');
 			$('#formFields').show();
-			$('#procedureForm').attr('action', '/admin/procedures');
+			$('#procedureForm').attr('action', '/admin/procedures/edit');
 		});
 	}
 	
@@ -344,6 +433,18 @@ ${id}-->${idSeg}`;
 		img.src = svgBase64;
 	}
 			
+	function checkProcedureId(id) {
+		$.get("/admin/checkProcedureID", {id: id}, function(data){
+			if(data) {
+				$('#idProces').addClass('is-invalid');
+				$('#formSubmit').prop('disabled', true);
+			} else {
+				$('#idProces').removeClass('is-invalid');
+				$('#formSubmit').prop('disabled', false);
+			}
+		});
+	}
+
 	</script>
 
 </body>

+ 1 - 1
src/main/resources/templates/components/form_procedure.html

@@ -29,7 +29,7 @@
 				<div class="form-group row">
 					<div class="col-2">
 						<label th:text="#{admin.procedures.form.id}">ID</label>
-						<input type="text" name="idProces" id="idProces" class="form-control" th:value="${procedure.idProces}" required th:attr="readonly=(${action} == 'edit' ? 'true' : 'false')">
+						<input type="text" name="idProces" id="idProces" class="form-control" onchange="checkProcedureId(this.value);" required th:attr="readonly=(${action} == 'edit' ? 'true' : 'false')">
 					</div>
 					<div class="col-4">
 						<label th:text="#{admin.procedures.form.nameProcedure}">Código</label>

+ 87 - 26
src/main/resources/templates/dashboardCentre.html

@@ -67,19 +67,19 @@
                     <div class="card card-default card-body" style="padding: 0 !important;">
                         <ul id="tabsJustified" class="nav nav-tabs nav-justified">
                         	<li class="nav-item">
-                                <a class="nav-link active" href="" data-target="#tab0" data-toggle="tab" th:text="#{dashboard.menu.summary}">Resumen</a>
+                                <a class="nav-link active" href="" data-target="#tab0" data-toggle="tab" th:text="#{dashboard.menu.summary}" onclick="window.location.hash = 'tab0'">Resumen</a>
                             </li>
-                            <li class="nav-item">
-                                <a class="nav-link" href="" data-target="#tab1" data-toggle="tab" th:text="#{dashboard.menu.procedures}">Procedimientos SAIC</a>
+                        	<li class="nav-item">
+                                <a class="nav-link" href="" data-target="#tab1" data-toggle="tab" th:text="#{dashboard.menu.procedures}" onclick="window.location.hash = 'tab1'">Procedimientos SAIC</a>
                             </li>
                             <li class="nav-item" onclick="adjustTableHeader();">
-                                <a class="nav-link" href="" data-target="#tab2" data-toggle="tab" th:text="#{dashboard.menu.docs}">Seguimiento/Acreditación</a>
+                                <a class="nav-link" href="" data-target="#tab2" data-toggle="tab" th:text="#{dashboard.menu.docs}" onclick="window.location.hash = 'tab2';">Seguimiento/Acreditación</a>
                             </li>
-                            <li class="nav-item">
-                                <a class="nav-link" href="" data-target="#tab3" data-toggle="tab" th:text="#{dashboard.menu.data}">Indicadores</a>
+							<li class="nav-item">
+                                <a class="nav-link" href="" data-target="#tab3" data-toggle="tab" th:text="#{dashboard.menu.data}" onclick="window.location.hash = 'tab3'">Indicadores</a>
                             </li>
                             <li class="nav-item">
-                                <a class="nav-link" href="" data-target="#tab4" data-toggle="tab" th:text="#{dashboard.menu.managers}">Responsables</a>
+                                <a class="nav-link" href="" data-target="#tab4" data-toggle="tab" th:text="#{dashboard.menu.managers}" onclick="window.location.hash = 'tab4'">Responsables</a>
                             </li>
                         </ul>
                         <!--/tabs-->
@@ -263,18 +263,19 @@
 	                            </div>
                             </div>
                             <div class="tab-pane" id="tab4">
-								<span class="btn btn-primary pointer" id="btnAddManager" style="float:right; cursor:pointer; margin-right:20px;" data-toggle="modal" data-target="#newRoleModal" th:text="#{admin.managers.newRole}">Añadir responsable</span>
+								<span class="btn btn-primary pointer" id="btnAddManager" style="z-index:100;float:right;margin-right:25px;font-size:75%;padding: 5px 8px 3px 8px;" data-toggle="modal" data-target="#newRoleModal"><i class="fa fa-plus"></i></span>
 								<div class="uv-table-group" th:if="${results}" style="cursor: auto; width: 90%;">
-
 									<div class="col-sm-12 uv-table-section" th:each="item : ${resp_titulacions}" style="display: flex; align-items: flex-start; margin-bottom: 15px;">
-    									<form enctype='multipart/form-data' method="POST" action="/admin/userrole/remove" style="margin-right: 15px;" onsubmit="var ok = confirm('¿Confirma que desea eliminar este responsable?');">
-											<input type="hidden" name="idRol" th:value="${item.rol.idRol}">
-											<input type="hidden" name="usuari" th:value="${item.usuari.usuari}">
-											<input type="hidden" name="tlugar" th:value="${item.organ.tlugar}">
-											<input type="hidden" name="lugar" th:value="${item.organ.lugar}">
-											<button class="btn" style="width:40px; height:38px; color: red;" th:title="#{admin.action.delete}">
-												<i class="fas fa-times"></i>
-											</button>
+    									<form id="deleteUserForm" enctype='multipart/form-data' onsubmit="deleteUserRole(event)" style="margin-right: 15px;">
+											<div class="form-group">
+												<input type="hidden" name="idRol" th:value="${item.rol.idRol}">
+												<input type="hidden" name="usuari" th:value="${item.usuari.usuari}">
+												<input type="hidden" name="tlugar" th:value="${item.organ.tlugar}">
+												<input type="hidden" name="lugar" th:value="${item.organ.lugar}">
+												<button class="btn" style="width:40px; height:38px; color: red;" th:title="#{admin.action.delete}" >
+													<i class="fas fa-times"></i>
+												</button>
+											</div>
 										</form>
 
 										<div>
@@ -285,14 +286,16 @@
 										</div>
 									</div>
 									<div class="col-sm-12 uv-table-section" th:each="item : ${resp_centres}" style="display: flex; align-items: flex-start; margin-bottom: 15px;">
-    									<form enctype='multipart/form-data' method="POST" action="/admin/userrole/remove" style="margin-right: 15px;" onsubmit="return confirm('¿Confirma que desea eliminar este responsable?');">
-											<input type="hidden" name="idRol" th:value="${item.rol.idRol}">
-											<input type="hidden" name="usuari" th:value="${item.usuari.usuari}">
-											<input type="hidden" name="tlugar" th:value="${item.organ.tlugar}">
-											<input type="hidden" name="lugar" th:value="${item.organ.lugar}">
-											<button class="btn" style="width:40px; height:38px; color: red;" th:title="#{admin.action.delete}">
-												<i class="fas fa-times"></i>
-											</button>
+    									<form id="deleteUserForm" enctype='multipart/form-data' onsubmit="deleteUserRole(event)" style="margin-right: 15px;">
+											<div class="form-group">
+												<input type="hidden" name="idRol" th:value="${item.rol.idRol}">
+												<input type="hidden" name="usuari" th:value="${item.usuari.usuari}">
+												<input type="hidden" name="tlugar" th:value="${item.organ.tlugar}">
+												<input type="hidden" name="lugar" th:value="${item.organ.lugar}">
+												<button class="btn" style="width:40px; height:38px; color: red;" th:title="#{admin.action.delete}" >
+													<i class="fas fa-times"></i>
+												</button>
+											</div>
 										</form>
 
 										<div>
@@ -423,7 +426,7 @@
 	        </div>
 	        <div class="modal-body">
 	        	<div class="container-fluid">
-			    	<form id="newRoleForm" enctype='multipart/form-data' method="POST" action="/admin/userrole/new" style="margin-top:25px;">
+			    	<form id="newRoleForm" enctype='multipart/form-data' onsubmit="addUserRole(event)" style="margin-top:25px;">
 						<input type="hidden" name="centre" th:value="${organ.lugar2}">
 						<input type="hidden" name="titulacio" th:value="${organ.lugar}">
 						<div class="form-group">
@@ -594,6 +597,14 @@
 			});
 					
 			editableSettings();
+
+			var hash = window.location.hash;
+			if(hash){
+				$('.nav-link').removeClass('active');
+				$('.tab-pane').removeClass('active');
+				$('.nav-link[href="'+hash+'"]').addClass('active');
+				$(hash).addClass('active');
+			}
 			
 		});		
 		
@@ -1059,6 +1070,56 @@
 			$('#email').val($('#username').val()+'@uv.es');
 		}
 
+
+		function addUserRole(event){
+		event.preventDefault(); // Evita que la página se recargue
+
+		const form = document.getElementById('newRoleForm');
+		const formData = new FormData(form);
+
+		$.ajax({
+			type: "POST",
+			url: "/admin/userrole/new",
+			data: formData,
+			processData: false, // Necesario para FormData
+			contentType: false, // Necesario para FormData
+			success: function(response) {
+				if (response === true) {
+					location.reload(); 
+				} else {
+					alert("Error al añadir el rol");
+				}
+			},
+			error: function() {
+				alert("No se pudo conectar con el servidor");
+			}
+		});
+	}
+
+	function deleteUserRole(event){
+		event.preventDefault(); // Evita que la página se recargue
+
+		const form = document.getElementById('deleteUserForm');
+		const formData = new FormData(form);
+
+		$.ajax({
+			type: "POST",
+			url: "/admin/userrole/remove",
+			data: formData,
+			processData: false, // Necesario para FormData
+			contentType: false, // Necesario para FormData
+			success: function(response) {
+				if (response === true) {
+					location.reload(); 
+				} else {
+					alert("Error al eliminar el rol");
+				}
+			},
+			error: function() {
+				alert("No se pudo conectar con el servidor");
+			}
+		});
+	}
 	</script>
 
 </body>

+ 87 - 25
src/main/resources/templates/dashboardTitulacio.html

@@ -78,19 +78,19 @@
                     <div class="card card-default card-body" style="padding: 0 !important;">
                         <ul id="tabsJustified" class="nav nav-tabs nav-justified">
                         	<li class="nav-item">
-                                <a class="nav-link active" href="" data-target="#tab0" data-toggle="tab" th:text="#{dashboard.menu.summary}">Resumen</a>
+                                <a class="nav-link active" href="" data-target="#tab0" data-toggle="tab" th:text="#{dashboard.menu.summary}" onclick="window.location.hash = 'tab0'">Resumen</a>
                             </li>
-                            <li class="nav-item">
-                                <a class="nav-link" href="" data-target="#tab1" data-toggle="tab" th:text="#{dashboard.menu.procedures}">Procedimientos SAIC</a>
+                        	<li class="nav-item">
+                                <a class="nav-link" href="" data-target="#tab1" data-toggle="tab" th:text="#{dashboard.menu.procedures}" onclick="window.location.hash = 'tab1'">Procedimientos SAIC</a>
                             </li>
                             <li class="nav-item">
-                                <a class="nav-link" href="" data-target="#tab2" data-toggle="tab" th:text="#{dashboard.menu.docs}">Seguimiento/Acreditación</a>
+                                <a class="nav-link" href="" data-target="#tab2" data-toggle="tab" th:text="#{dashboard.menu.docs}" onclick="window.location.hash = 'tab2'">Acreditación</a>
                             </li>
-                            <li class="nav-item">
-                                <a class="nav-link" href="" data-target="#tab3" data-toggle="tab" th:text="#{dashboard.menu.data}">Indicadores</a>
+							<li class="nav-item">
+                                <a class="nav-link" href="" data-target="#tab3" data-toggle="tab" th:text="#{dashboard.menu.data}" onclick="window.location.hash = 'tab3'">Indicadores</a>
                             </li>
                             <li class="nav-item">
-                                <a class="nav-link" href="" data-target="#tab4" data-toggle="tab" th:text="#{dashboard.menu.managers}">Responsables</a>
+                                <a class="nav-link" href="" data-target="#tab4" data-toggle="tab" th:text="#{dashboard.menu.managers}" onclick="window.location.hash = 'tab4'">Responsables</a>
                             </li>
                         </ul>
                         <!--/tabs-->
@@ -287,17 +287,19 @@
 	                            </div>
                             </div>
                             <div class="tab-pane" id="tab4">
-								<span class="btn btn-primary pointer" th:if="${#authentication.principal.isAdmin() or #authentication.principal.isGranted()}" id="btnAddManager" style="float:right; cursor:pointer; margin-right:20px;" data-toggle="modal" data-target="#newRoleModal" th:text="#{admin.managers.newRole}">Añadir responsable</span>
+								<span class="btn btn-primary pointer" th:if="${#authentication.principal.isAdmin() or #authentication.principal.isGranted()}" id="btnAddManager" style="z-index:100;float:right;margin-right:25px;font-size:75%;padding: 5px 8px 3px 8px;" data-toggle="modal" data-target="#newRoleModal"><i class="fa fa-plus"></i></span>
                                 <div class="uv-table-group" th:if="${results}" style="cursor: auto;  width: 90%">
 									<div class="col-sm-12 uv-table-section" th:each="item : ${resp_titulacions}" style="display: flex; align-items: flex-start; margin-bottom: 15px;">
-										<form enctype='multipart/form-data' method="POST" action="/admin/userrole/remove" style="margin-right: 15px;" th:if="${#authentication.principal.isAdmin() or #authentication.principal.isGranted()}" onsubmit="return confirm('¿Confirma que desea eliminar este responsable?');">
-											<input type="hidden" name="idRol" th:value="${item.rol.idRol}">
-											<input type="hidden" name="usuari" th:value="${item.usuari.usuari}">
-											<input type="hidden" name="tlugar" th:value="${item.organ.tlugar}">
-											<input type="hidden" name="lugar" th:value="${item.organ.lugar}">
-											<button class="btn" style="width:40px; height:38px; color: red;" th:title="#{admin.action.delete}">
-												<i class="fas fa-times"></i>
-											</button>
+    									<form id="deleteUserForm" enctype='multipart/form-data' onsubmit="deleteUserRole(event)" style="margin-right: 15px;">
+											<div class="form-group">
+												<input type="hidden" name="idRol" th:value="${item.rol.idRol}">
+												<input type="hidden" name="usuari" th:value="${item.usuari.usuari}">
+												<input type="hidden" name="tlugar" th:value="${item.organ.tlugar}">
+												<input type="hidden" name="lugar" th:value="${item.organ.lugar}">
+												<button class="btn" style="width:40px; height:38px; color: red;" th:title="#{admin.action.delete}" >
+													<i class="fas fa-times"></i>
+												</button>
+											</div>
 										</form>
 										<div>
 											<strong th:text="${#locale.language} == 'es' ? ${item.rol.descripcioCas}:${item.rol.descripcioVal}"></strong>
@@ -307,14 +309,16 @@
 										</div>									
 									</div>
 									<div class="col-sm-12 uv-table-section" th:each="item : ${resp_centres}" style="display: flex; align-items: flex-start; margin-bottom: 15px;">
-										<form enctype='multipart/form-data' method="POST" action="/admin/userrole/remove" style="margin-right: 15px;" th:if="${#authentication.principal.isAdmin() or #authentication.principal.isGranted()}" onsubmit="return confirm('¿Confirma que desea eliminar este responsable?');">
-											<input type="hidden" name="idRol" th:value="${item.rol.idRol}">
-											<input type="hidden" name="usuari" th:value="${item.usuari.usuari}">
-											<input type="hidden" name="tlugar" th:value="${item.organ.tlugar}">
-											<input type="hidden" name="lugar" th:value="${item.organ.lugar}">
-											<button class="btn" style="width:40px; height:38px; color: red;" th:title="#{admin.action.delete}">
-												<i class="fas fa-times"></i>
-											</button>
+										<form id="deleteUserForm" enctype='multipart/form-data' onsubmit="deleteUserRole(event)" style="margin-right: 15px;">
+											<div class="form-group">
+												<input type="hidden" name="idRol" th:value="${item.rol.idRol}">
+												<input type="hidden" name="usuari" th:value="${item.usuari.usuari}">
+												<input type="hidden" name="tlugar" th:value="${item.organ.tlugar}">
+												<input type="hidden" name="lugar" th:value="${item.organ.lugar}">
+												<button class="btn" style="width:40px; height:38px; color: red;" th:title="#{admin.action.delete}" >
+													<i class="fas fa-times"></i>
+												</button>
+											</div>
 										</form>
 										<div>
 										<div>
@@ -597,7 +601,7 @@
 	        </div>
 	        <div class="modal-body">
 	        	<div class="container-fluid">
-			    	<form id="newRoleForm" enctype='multipart/form-data' method="POST" action="/admin/userrole/new" style="margin-top:25px;">
+			    	<form id="newRoleForm" enctype='multipart/form-data' onsubmit="addUserRole(event)" style="margin-top:25px;">
 						<input type="hidden" name="centre" th:value="${organ.lugar2}">  
 			    		<input type="hidden" name="titulacio" th:value="${organ.lugar}">	
 						<div class="form-group">
@@ -1141,6 +1145,14 @@
 						
 			editableSettings();
 			loadLinks();
+
+			var hash = window.location.hash;
+			if(hash){
+				$('.nav-link').removeClass('active');
+				$('.tab-pane').removeClass('active');
+				$('.nav-link[href="'+hash+'"]').addClass('active');
+				$(hash).addClass('active');
+			}
 		});		
 		
 		function createChartCanvas(i){
@@ -1384,6 +1396,56 @@
 			$('#email').val($('#username').val()+'@uv.es');
 		}
 
+		function addUserRole(event){
+		event.preventDefault(); // Evita que la página se recargue
+
+		const form = document.getElementById('newRoleForm');
+		const formData = new FormData(form);
+
+		$.ajax({
+			type: "POST",
+			url: "/admin/userrole/new",
+			data: formData,
+			processData: false, // Necesario para FormData
+			contentType: false, // Necesario para FormData
+			success: function(response) {
+				if (response === true) {
+					location.reload(); 
+				} else {
+					alert("Error al añadir el rol");
+				}
+			},
+			error: function() {
+				alert("No se pudo conectar con el servidor");
+			}
+		});
+	}
+
+	function deleteUserRole(event){
+		event.preventDefault(); // Evita que la página se recargue
+
+		const form = document.getElementById('deleteUserForm');
+		const formData = new FormData(form);
+
+		$.ajax({
+			type: "POST",
+			url: "/admin/userrole/remove",
+			data: formData,
+			processData: false, // Necesario para FormData
+			contentType: false, // Necesario para FormData
+			success: function(response) {
+				if (response === true) {
+					location.reload(); 
+				} else {
+					alert("Error al eliminar el rol");
+				}
+			},
+			error: function() {
+				alert("No se pudo conectar con el servidor");
+			}
+		});
+	}
+
 	</script>
 
 </body>

+ 76 - 15
src/main/resources/templates/dashboardUniversitat.html

@@ -66,19 +66,19 @@
                     <div class="card card-default card-body" style="padding: 0 !important;">
                         <ul id="tabsJustified" class="nav nav-tabs nav-justified">
                         	<li class="nav-item">
-                                <a class="nav-link active" href="" data-target="#tab3" data-toggle="tab" th:text="#{dashboard.menu.tits}">Titulaciones</a>
+                                <a class="nav-link active" href="" data-target="#tab3" data-toggle="tab" th:text="#{dashboard.menu.tits}" onclick="window.location.hash = 'tab3'">Titulaciones</a>
                             </li>
                         	<li class="nav-item">
-                                <a class="nav-link" href="" data-target="#tab0" data-toggle="tab" th:text="#{dashboard.menu.summary}">Resumen</a>
+                                <a class="nav-link" href="" data-target="#tab0" data-toggle="tab" th:text="#{dashboard.menu.summary}" onclick="window.location.hash = 'tab0'">Resumen</a>
                             </li>
                             <li class="nav-item">
-                                <a class="nav-link" href="" data-target="#tab1" data-toggle="tab" th:text="#{dashboard.menu.procedures}">Procedimientos SAIC</a>
+                                <a class="nav-link" href="" data-target="#tab1" data-toggle="tab" th:text="#{dashboard.menu.procedures}" onclick="window.location.hash = 'tab1'">Procedimientos SAIC</a>
                             </li>
 							<li class="nav-item">
-                                <a class="nav-link" href="" data-target="#tab2" data-toggle="tab" th:text="#{dashboard.menu.docs}">Seguimiento/Acreditación</a>
+                                <a class="nav-link" href="" data-target="#tab2" data-toggle="tab" th:text="#{dashboard.menu.docs}" onclick="window.location.hash = 'tab2'">Seguimiento/Acreditación</a>
                             </li>
                             <li class="nav-item">
-                                <a class="nav-link" href="" data-target="#tab4" data-toggle="tab" th:text="#{dashboard.menu.managers}">Responsables</a>
+                                <a class="nav-link" href="" data-target="#tab4" data-toggle="tab" th:text="#{dashboard.menu.managers}" onclick="window.location.hash = 'tab4'">Responsables</a>
                             </li>
                         </ul>
                         <!--/tabs-->
@@ -197,7 +197,7 @@
 	                            </div>
                             </div>
                             <div class="tab-pane" id="tab4">
-								<span class="btn btn-primary pointer" id="btnAddManager" style="float:right; cursor:pointer; margin-right:20px;" data-toggle="modal" data-target="#newRoleModal" th:text="#{admin.managers.newRole}">Añadir responsable</span>
+								<span class="btn btn-primary pointer" id="btnAddManager" style="z-index:100;float:right;margin-right:25px;font-size:75%;padding: 5px 8px 3px 8px;" data-toggle="modal" data-target="#newRoleModal"><i class="fa fa-plus"></i></span>
 								<div class="uv-table-group" th:if="${results}" style="cursor: auto; width: 90%;">
 									<div class="col-sm-12 uv-table-section" th:each="item : ${resp_titulacions}" style="margin-top:20px;">
 										<div class="col-sm-12 uv-table-section" th:each="item : ${resp_titulacions}" style="margin-top:20px;">
@@ -208,14 +208,16 @@
 										<a th:href="'mailto:'+${item.usuari.email}" th:title="${item.usuari.email}" th:text="${item.usuari.nom + ' ' + item.usuari.cognoms}"></a> (<small><span th:text="#{managers.since}">Desde el</span> <span th:text="${item.inici}"></span></small>)
 									</div>
 									<div class="col-sm-12 uv-table-section" th:each="item : ${resp_centres}" style="display: flex; align-items: flex-start; margin-bottom: 15px;">
-    									<form enctype='multipart/form-data' method="POST" action="/admin/userrole/remove" style="margin-right: 15px;" onsubmit="return confirm('¿Confirma que desea eliminar este responsable?');">
-											<input type="hidden" name="idRol" th:value="${item.rol.idRol}">
-											<input type="hidden" name="usuari" th:value="${item.usuari.usuari}">
-											<input type="hidden" name="tlugar" th:value="${item.organ.tlugar}">
-											<input type="hidden" name="lugar" th:value="${item.organ.lugar}">
-											<button class="btn" style="width:40px; height:38px; color: red;" th:title="#{admin.action.delete}">
-												<i class="fas fa-times"></i>
-											</button>
+    									<form id="deleteUserForm" enctype='multipart/form-data' onsubmit="deleteUserRole(event)" style="margin-right: 15px;">
+											<div class="form-group">
+												<input type="hidden" name="idRol" th:value="${item.rol.idRol}">
+												<input type="hidden" name="usuari" th:value="${item.usuari.usuari}">
+												<input type="hidden" name="tlugar" th:value="${item.organ.tlugar}">
+												<input type="hidden" name="lugar" th:value="${item.organ.lugar}">
+												<button class="btn" style="width:40px; height:38px; color: red;" th:title="#{admin.action.delete}" >
+													<i class="fas fa-times"></i>
+												</button>
+											</div>
 										</form>
 
 										<div>
@@ -395,7 +397,7 @@
 	        </div>
 	        <div class="modal-body">
 	        	<div class="container-fluid">
-			    	<form id="newRoleForm" enctype='multipart/form-data' method="POST" action="/admin/userrole/new" style="margin-top:25px;">
+			    	<form id="newRoleForm" enctype='multipart/form-data' onsubmit="addUserRole(event)" style="margin-top:25px;">
 						<input type="hidden" name="centre" th:value="${organ.lugar2}">
 						<input type="hidden" name="titulacio" th:value="${organ.lugar}">  
 
@@ -567,6 +569,15 @@
 			editableSettings();
 			
 			layout.closeLoadingSpinner(".uv-loading-spinner");
+
+			var hash = window.location.hash;
+			if(hash){
+				$('.nav-link').removeClass('active');
+				$('.tab-pane').removeClass('active');
+				$('.nav-link[href="'+hash+'"]').addClass('active');
+				$(hash).addClass('active');
+			}
+			
 		});	
 		
 		function initTables(){
@@ -1012,6 +1023,56 @@
 			$('#email').val($('#username').val()+'@uv.es');
 		}
 
+		function addUserRole(event){
+			event.preventDefault(); // Evita que la página se recargue
+
+			const form = document.getElementById('newRoleForm');
+			const formData = new FormData(form);
+
+			$.ajax({
+				type: "POST",
+				url: "/admin/userrole/new",
+				data: formData,
+				processData: false, // Necesario para FormData
+				contentType: false, // Necesario para FormData
+				success: function(response) {
+					if (response === true) {
+						location.reload(); 
+					} else {
+						alert("Error al añadir el rol");
+					}
+				},
+				error: function() {
+					alert("No se pudo conectar con el servidor");
+				}
+			});
+		}
+
+		function deleteUserRole(event){
+			event.preventDefault(); // Evita que la página se recargue
+
+			const form = document.getElementById('deleteUserForm');
+			const formData = new FormData(form);
+
+			$.ajax({
+				type: "POST",
+				url: "/admin/userrole/remove",
+				data: formData,
+				processData: false, // Necesario para FormData
+				contentType: false, // Necesario para FormData
+				success: function(response) {
+					if (response === true) {
+						location.reload(); 
+					} else {
+						alert("Error al eliminar el rol");
+					}
+				},
+				error: function() {
+					alert("No se pudo conectar con el servidor");
+				}
+			});
+		}
+
 	</script>
 
 </body>