rajah 6 kuukautta sitten
vanhempi
commit
b6d205c3bf

+ 1 - 0
src/app/composants/poll-booth/poll-booth.component.html

@@ -8,6 +8,7 @@
   <div class="card-header shadow-sm">
 		<button type="button" (click)="goToListVotes()" class="btn bg-gradient btn-primary btn-sm" style="margin-right: 5px;"><i class="fa-solid fa-xmark"></i>&nbsp;<span i18n>Retour</span></button>
     @if (nombreChoixRestant != -1) { <button type="button" [disabled]="chosenProductions.length == 0" class="btn bg-gradient btn-danger btn-sm" style="margin-right: 5px;" data-bs-toggle="modal" data-bs-target="#modalValider"><i class="fa-solid fa-check"></i>&nbsp;<span i18n>Valider</span></button> }
+    @if (nombreChoixRestant == -1) { <button type="button" disabled class="btn bg-gradient btn-success btn-sm" style="margin-right: 5px;"><i class="fa-solid fa-check"></i>&nbsp;<span i18n>Validé</span></button> }
 	</div>
   <div class="card-body hstack">
     <div><small><span i18n>Productions préférées</span></small></div>

+ 41 - 11
src/app/composants/poll-list/poll-list.component.html

@@ -1,30 +1,42 @@
 <app-menu></app-menu>
 <div id="main">
   <div class="card shadow">
-    <div class="card-header"><span i18n>Votes</span></div>
+    <div class="card-header"><span i18n>Voter</span></div>
   	<div class="card-header shadow-sm">
   		<div class="row">
   			<div class="form-group col-sm-4 label-nobr">
   				<button type="button" (click)="goToRefreshListCategorie()" class="btn bg-gradient btn-primary btn-sm" style="margin-right: 5px;"><i class="fa-solid fa-rotate"></i>&nbsp;<span i18n>Actualiser</span></button>
-          @if (this.logged && (this.role === "ADMIN")) { <button type="button" #boutonCloturer class="btn bg-gradient btn-danger btn-sm disabled" style="margin-right: 5px;" data-bs-toggle="modal" data-bs-target="#modalFermerVotes"><i class="fa-solid fa-lock"></i>&nbsp;<span i18n>Clôturer les votes</span></button> }
+          @if (this.logged && (this.role === "ADMIN")) {
+          <button type="button" #boutonCloturer class="btn bg-gradient btn-danger btn-sm disabled" style="margin-right: 5px;" i18n-tootip tooltip="Fermeture des urnes et validation automatique des choix" placement="right" container="body" data-bs-toggle="modal" data-bs-target="#modalFermerVotes"><i class="fa-solid fa-lock"></i>&nbsp;<span i18n>Clôturer les votes</span></button>
+          &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+          <button type="button" #imprimerResultats (click)="getResultatsPDF()" class="btn bg-gradient btn-primary btn-sm disabled" style="margin-right: 5px;" i18n-tootip tooltip="Télécharger le PDF contenant les résultats pour impression" placement="right" container="body"><i class="fa-solid fa-file-pdf"></i>&nbsp;<span i18n>Imprimer les résultats</span></button>
+          <button type="button" #boutonResultats class="btn bg-gradient btn-danger btn-sm disabled" style="margin-right: 5px;" i18n-tootip tooltip="Publier l'ensemble des résultats aux participants" placement="right" container="body" data-bs-toggle="modal" data-bs-target="#modalAfficherResultats"><i class="fa-solid fa-trophy"></i>&nbsp;<span i18n>Afficher les résultats</span></button>
+          }
   			</div>
   		</div>
   	</div>
-  	<div class="card-body">
-  		<table class="table table-sm">
-  			<thead class="thead-dark">
-  				<tr>
-            <th class="fs-6 label-nobr"><small><span i18n>Catégorie</span></small></th>
-  				</tr>
-  			</thead>
+    <div class="card-body"><br/>
+      @if (this.affiches > 0) {
+      <table>
   			<tbody>
-  				@for (categorie of categories; track categorie.numeroCategorie) { @if (categorie.pollable) {
+          @for (categorie of categories; track categorie.numeroCategorie) { @if (categorie.pollable || (this.logged && (this.role === "ADMIN") && (categorie.computed))) {
   				<tr>
-            <td class="label-nobr"><a (click)="voteCategorie(categorie.numeroCategorie)" class="link-primary pointeur-souris text-decoration-none">{{ categorie.libelle }}</a>&nbsp;&nbsp;</td>
+            <td class="label-nobr fs-3" style="height:30px;vertical-align:center;">
+              <i class="fa-solid fa-list-ol text-body-tertiary"></i>&nbsp;<a (click)="voteCategorie(categorie.numeroCategorie)" class="link-primary pointeur-souris text-decoration-none">{{ categorie.libelle }}</a>
+            </td>
+            <td class="label-nobr" style="height:30px;vertical-align:center;">
+              @if (this.logged && (this.role === "ADMIN") && (categorie.computed)) {
+              <button type="button" (click)="getDiaporama(categorie.numeroCategorie, categorie.libelle)" class="btn bg-gradient btn-primary btn-sm" style="margin-left: 48px;" i18n-tootip tooltip="Télécharger le diaporama HTML contenant les résultats de cette catégorie" placement="right" container="body"><i class="fa-solid fa-file-code"></i>&nbsp;<span i18n>Diaporama</span></button>
+              }
+            </td>
   				</tr>
           } }
   			</tbody>
   		</table>
+      }
+      @else {
+      <span i18n><br/>Aucune catégorie soumise au vote pour le moment.<br/><br/></span>
+      }<br/>
   	</div>
   </div>
 
@@ -46,4 +58,22 @@
       </div>
     </div>
   </div>
+  <div class="modal fade" id="modalAfficherResultats" tabindex="-1" aria-labelledby="modalAfficherResultatsTitre" aria-hidden="true">
+    <div class="modal-dialog modal-dialog-centered" role="document">
+      <div class="modal-content">
+        <div class="modal-header">
+          <h5 class="modal-title text-danger" id="modalAfficherResultatsTitre"><span i18n>Publier les résultats</span></h5>
+        </div>
+        <div class="modal-body">
+          <span i18n>
+            Pour l'ensemble des catégories.
+          </span>
+        </div>
+        <div class="modal-footer">
+          <button type="button" class="btn bg-gradient btn-secondary btn-sm" data-bs-dismiss="modal"><span i18n>Annuler</span></button>
+          <button type="button" class="btn bg-gradient btn-danger btn-sm" (click)="publierVotes()" data-bs-dismiss="modal"><span i18n>Confirmer</span></button>
+        </div>
+      </div>
+    </div>
+  </div>
 </div>

+ 32 - 3
src/app/composants/poll-list/poll-list.component.ts

@@ -1,29 +1,36 @@
 import { Component, OnInit, ViewChild, ElementRef, Renderer2 } from '@angular/core';
 import { Router, ActivatedRoute } from '@angular/router';
+import { TooltipModule } from 'ngx-bootstrap/tooltip';
+import { saveAs } from 'file-saver';
 
 import { MenuComponent } from '../menu/menu.component';
 import { Categorie } from '../../interfaces/categorie';
 import { CategorieService } from '../../services/categorie.service';
 import { AccountService } from '../../services/account.service';
+import { BulletinService } from '../../services/bulletin.service';
 
-@Component({ selector: 'app-poll-list', imports: [MenuComponent], templateUrl: './poll-list.component.html', styleUrl: './poll-list.component.css' })
+@Component({ selector: 'app-poll-list', imports: [TooltipModule, MenuComponent], templateUrl: './poll-list.component.html', styleUrl: './poll-list.component.css' })
 
 export class PollListComponent implements OnInit
 {
 
   @ViewChild('boutonCloturer', {static: false}) boutonCloturer!: ElementRef;
+  @ViewChild('imprimerResultats', {static: false}) imprimerResultats!: ElementRef;
+  @ViewChild('boutonResultats', {static: false}) boutonResultats!: ElementRef;
 
   logged: boolean = false;
   role: string = "";
 
   categories: Categorie[] = [];
   affiches: number = 0;
+  calcules: number = 0;
 
   constructor(
     private categorieService: CategorieService,
     private router: Router,
     private route: ActivatedRoute,
     private accountService: AccountService,
+    private bulletinService: BulletinService,
     private renderer: Renderer2
   ) { }
 
@@ -39,15 +46,29 @@ export class PollListComponent implements OnInit
   {
     this.categorieService.getListCategorie(false).subscribe(data => {
       this.affiches = 0;
+      this.calcules = 0;
       this.categories = data;
       if (this.categories)
       {
         if (this.categories.length > 0)
         {
-          for (let i = 0; i < this.categories.length; i++) { if (this.categories[i].pollable) { this.affiches++; } }
+          for (let i = 0; i < this.categories.length; i++)
+          {
+            if (this.categories[i].pollable) { this.affiches++; }
+            if (this.categories[i].computed) { this.calcules++; }
+          }
         }
       }
-      if (this.affiches > 0) { this.renderer.removeClass(this.boutonCloturer.nativeElement, 'disabled'); }
+      if (this.affiches > 0)
+      {
+        this.renderer.removeClass(this.boutonCloturer.nativeElement, 'disabled');
+      }
+      if (this.calcules > 0)
+      {
+        this.renderer.addClass(this.boutonCloturer.nativeElement, 'disabled');
+        this.renderer.removeClass(this.imprimerResultats.nativeElement, 'disabled');
+        this.renderer.removeClass(this.boutonResultats.nativeElement, 'disabled');
+      }
     });
   }
 
@@ -57,4 +78,12 @@ export class PollListComponent implements OnInit
 
   fermerVotes() { if (this.logged && (this.role === "ADMIN")) { this.categorieService.cloreScrutins().subscribe(() => { this.goToRefreshListCategorie(); }); } }
 
+  getResultatsPDF() { this.bulletinService.getResultatsPDF().subscribe(response => { this.savePDF(response.body, 'resultats.pdf'); }); }
+  savePDF(data: any, filename?: string) { const blob = new Blob([data], {type: 'application/pdf'}); saveAs(blob, filename); }
+
+  getDiaporama(id: number, nom: string) { this.bulletinService.getResultatsHTML(id).subscribe(response => { this.saveHTML(response.body, nom + '.html'); }); }
+  saveHTML(data: any, filename?: string) { const blob = new Blob([data], {type: 'text/html'}); saveAs(blob, filename); }
+
+  publierVotes() { if (this.logged && (this.role === "ADMIN")) { this.categorieService.publierVotes().subscribe(() => { this.goToRefreshListCategorie(); }); } }
+
 }

+ 2 - 2
src/app/composants/show-list/show-list.component.html

@@ -6,7 +6,7 @@
 		<div class="row">
 			<div class="form-group col-sm-4 label-nobr">
 				<button type="button" (click)="goToRefreshListCategorie()" class="btn bg-gradient btn-primary btn-sm" style="margin-right: 5px;"><i class="fa-solid fa-rotate"></i>&nbsp;<span i18n>Actualiser</span></button>
-        <button type="button" (click)="getVersionPDF()" class="btn bg-gradient btn-primary btn-sm" style="margin-right: 5px;" i18n-tootip tooltip="Télécharger la version PDF de cette page pour impression" placement="right" container="body"><i class="fa-solid fa-file-text"></i>&nbsp;<span i18n>Imprimer</span></button>
+        <button type="button" (click)="getVersionPDF()" class="btn bg-gradient btn-primary btn-sm" style="margin-right: 5px;" i18n-tootip tooltip="Télécharger la version PDF de cette page pour impression" placement="right" container="body"><i class="fa-solid fa-file-pdf"></i>&nbsp;<span i18n>Imprimer</span></button>
 			</div>
 		</div>
 	</div>
@@ -18,7 +18,7 @@
     {{ categorie.libelle }}
     @if (!categorie.pollable) { &nbsp;&nbsp;&nbsp;&nbsp;
       <button type="button" (click)="lierProductions(categorie.numeroCategorie)" class="btn bg-gradient btn-primary btn-sm" style="margin-right: 5px;" i18n-tootip tooltip="Rattacher des productions à cette catégorie et les ordonner" placement="right" container="body"><i class="fa-solid fa-link"></i>&nbsp;<span i18n>Rattacher</span></button>
-      <button type="button" (click)="getDiaporama(categorie.numeroCategorie, categorie.libelle)" class="btn bg-gradient btn-primary btn-sm" style="margin-right: 5px;" i18n-tootip tooltip="Télécharger le fichier de présentations de cette catégorie" placement="right" container="body"><i class="fa-solid fa-file-code"></i>&nbsp;<span i18n>Diaporama</span></button>
+      <button type="button" (click)="getDiaporama(categorie.numeroCategorie, categorie.libelle)" class="btn bg-gradient btn-primary btn-sm" style="margin-right: 5px;" i18n-tootip tooltip="Télécharger le fichier HTML de présentations de cette catégorie" placement="right" container="body"><i class="fa-solid fa-file-code"></i>&nbsp;<span i18n>Diaporama</span></button>
       <button type="button" [disabled]="productions.length == 0" class="btn bg-gradient btn-danger btn-sm" style="margin-right: 5px;" i18n-tootip tooltip="Ouvrir le scrutin, après que le présentateur a utilisé le diaporama publiquement" placement="right" container="body" data-bs-toggle="modal" [attr.data-bs-target]="'#modalOuvrirVotes' + categorie.numeroCategorie"><i class="fa-solid fa-check-to-slot"></i>&nbsp;<span i18n>Ouvrir les votes</span></button>
     }
     @if (categorie.uploadable) { }

+ 19 - 1
src/app/services/bulletin.service.ts

@@ -1,5 +1,5 @@
 import { Injectable } from '@angular/core';
-import { HttpClient, HttpParams } from '@angular/common/http'
+import { HttpClient, HttpParams, HttpHeaders, HttpResponse } from '@angular/common/http'
 import { Observable } from 'rxjs';
 
 import { Environnement } from '../env';
@@ -62,4 +62,22 @@ export class BulletinService
     return this.httpClient.get(`${this.baseURL}/down`, { params: params });
   }
 
+  getResultatsPDF(): Observable<HttpResponse<Blob>>
+  {
+    let headers = new HttpHeaders();
+
+    headers = headers.append('Accept', 'application/pdf');
+
+    return this.httpClient.get(`${this.baseURL}/file`, { headers: headers, observe: 'response', responseType: 'blob' });
+  }
+
+  getResultatsHTML(id: number): Observable<HttpResponse<Blob>>
+  {
+    let headers = new HttpHeaders();
+
+    headers = headers.append('Accept', 'text/html');
+
+    return this.httpClient.get(`${this.baseURL}/diapos/${id}`, { headers: headers, observe: 'response', responseType: 'blob' });
+  }
+
 }

+ 2 - 0
src/app/services/categorie.service.ts

@@ -34,4 +34,6 @@ export class CategorieService
 
   cloreScrutins(): Observable<Object>{ return this.httpClient.get(`${this.baseURL}/close-polls`); }
 
+  publierVotes(): Observable<Object>{ return this.httpClient.get(`${this.baseURL}/show-results`); }
+
 }