deploiement sur VM
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
.Poll_Informations {
|
||||
background-color: white;
|
||||
width: 100%;
|
||||
margin-top: 1rem;
|
||||
box-shadow: 0 2px 4px 0 rgba(0,0,0,0.10);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.Meal_Preferences {
|
||||
padding: 1rem;
|
||||
border-bottom: 2px solid #F0F4F8;
|
||||
}
|
||||
|
||||
.Poll_Has_Meal {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.Poll_Has_Meal {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.Poll_Description_Title {
|
||||
font-size: 0.8rem;
|
||||
color: #243B53;
|
||||
}
|
||||
|
||||
.Poll_Infos {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.Poll_Info {
|
||||
flex: 1;
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<div class="Container">
|
||||
|
||||
|
||||
<img src="../../assets/flat_logo.png" alt="Logo Simba" height="50px" [ngStyle]="{ 'marginBottom': '1rem' }" />
|
||||
|
||||
<!-- { isModalOpened &&
|
||||
<div className="modal" onClick={() => setIsModalOpened(false)}>
|
||||
<div className="Export_Modal" >
|
||||
<a className="Export Disabled" target="_blank" rel="noopener noreferrer">
|
||||
<svg aria-hidden="true" width="40px" height="40px" focusable="false" data-prefix="fas" data-icon="file-pdf" className="Export_Icon" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path fill="currentColor" d="M181.9 256.1c-5-16-4.9-46.9-2-46.9 8.4 0 7.6 36.9 2 46.9zm-1.7 47.2c-7.7 20.2-17.3 43.3-28.4 62.7 18.3-7 39-17.2 62.9-21.9-12.7-9.6-24.9-23.4-34.5-40.8zM86.1 428.1c0 .8 13.2-5.4 34.9-40.2-6.7 6.3-29.1 24.5-34.9 40.2zM248 160h136v328c0 13.3-10.7 24-24 24H24c-13.3 0-24-10.7-24-24V24C0 10.7 10.7 0 24 0h200v136c0 13.2 10.8 24 24 24zm-8 171.8c-20-12.2-33.3-29-42.7-53.8 4.5-18.5 11.6-46.6 6.2-64.2-4.7-29.4-42.4-26.5-47.8-6.8-5 18.3-.4 44.1 8.1 77-11.6 27.6-28.7 64.6-40.8 85.8-.1 0-.1.1-.2.1-27.1 13.9-73.6 44.5-54.5 68 5.6 6.9 16 10 21.5 10 17.9 0 35.7-18 61.1-61.8 25.8-8.5 54.1-19.1 79-23.2 21.7 11.8 47.1 19.5 64 19.5 29.2 0 31.2-32 19.7-43.4-13.9-13.6-54.3-9.7-73.6-7.2zM377 105L279 7c-4.5-4.5-10.6-7-17-7h-6v128h128v-6.1c0-6.3-2.5-12.4-7-16.9zm-74.1 255.3c4.1-2.7-2.5-11.9-42.8-9 37.1 15.8 42.8 9 42.8 9z"></path></svg>
|
||||
<span>PDF (Premium)</span>
|
||||
</a>
|
||||
<a className="Export" target="_blank" rel="noopener noreferrer" href={`${BASE_URL}/polls/${slug}/results`}>
|
||||
<svg aria-hidden="true" width="40px" height="40px" focusable="false" data-prefix="fas" data-icon="file-excel" className="Export_Icon" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path fill="currentColor" d="M224 136V0H24C10.7 0 0 10.7 0 24v464c0 13.3 10.7 24 24 24h336c13.3 0 24-10.7 24-24V160H248c-13.2 0-24-10.8-24-24zm60.1 106.5L224 336l60.1 93.5c5.1 8-.6 18.5-10.1 18.5h-34.9c-4.4 0-8.5-2.4-10.6-6.3C208.9 405.5 192 373 192 373c-6.4 14.8-10 20-36.6 68.8-2.1 3.9-6.1 6.3-10.5 6.3H110c-9.5 0-15.2-10.5-10.1-18.5l60.3-93.5-60.3-93.5c-5.2-8 .6-18.5 10.1-18.5h34.8c4.4 0 8.5 2.4 10.6 6.3 26.1 48.8 20 33.6 36.6 68.5 0 0 6.1-11.7 36.6-68.5 2.1-3.9 6.2-6.3 10.6-6.3H274c9.5-.1 15.2 10.4 10.1 18.4zM384 121.9v6.1H256V0h6.1c6.4 0 12.5 2.5 17 7l97.9 98c4.5 4.5 7 10.6 7 16.9z"></path></svg>
|
||||
<span>EXCEL</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
}-->
|
||||
|
||||
<app-top-bar [adminSlug]="poll?.slugAdmin" [slug]="poll?.slug" [padURL]="poll?.padURL" [talkToURL]="poll?.tlkURL" ></app-top-bar>
|
||||
|
||||
<p-card>
|
||||
<p-toast></p-toast>
|
||||
<ng-template pTemplate="title">
|
||||
<h1>{{poll?.title}}</h1>
|
||||
</ng-template>
|
||||
<ng-template pTemplate="subtitle">
|
||||
<div class="Dates"><span>Créé il y a {{poll?.createdAt | dateago}}</span></div>
|
||||
</ng-template>
|
||||
<ng-template pTemplate="content">
|
||||
<div class="Poll_Infos">
|
||||
<p class="Poll_Location"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24"
|
||||
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||||
class="feather feather-map-pin">
|
||||
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path>
|
||||
<circle cx="12" cy="10" r="3"></circle>
|
||||
</svg>{{poll?.location}}</p>
|
||||
<div *ngIf="poll?.has_meal" class="Poll_Has_Meal"><svg class="feather" aria-hidden="true" width="20" height="20"
|
||||
focusable="false" data-prefix="fas" data-icon="utensils" role="img" xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 416 512">
|
||||
<path fill="currentColor"
|
||||
d="M207.9 15.2c.8 4.7 16.1 94.5 16.1 128.8 0 52.3-27.8 89.6-68.9 104.6L168 486.7c.7 13.7-10.2 25.3-24 25.3H80c-13.7 0-24.7-11.5-24-25.3l12.9-238.1C27.7 233.6 0 196.2 0 144 0 109.6 15.3 19.9 16.1 15.2 19.3-5.1 61.4-5.4 64 16.3v141.2c1.3 3.4 15.1 3.2 16 0 1.4-25.3 7.9-139.2 8-141.8 3.3-20.8 44.7-20.8 47.9 0 .2 2.7 6.6 116.5 8 141.8.9 3.2 14.8 3.4 16 0V16.3c2.6-21.6 44.8-21.4 48-1.1zm119.2 285.7l-15 185.1c-1.2 14 9.9 26 23.9 26h56c13.3 0 24-10.7 24-24V24c0-13.2-10.7-24-24-24-82.5 0-221.4 178.5-64.9 300.9z">
|
||||
</path>
|
||||
</svg>
|
||||
Cet évènement contient un repas
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div >
|
||||
<div class="container-fluid">
|
||||
<div class="table-responsive-sm card">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th rowspan="2"></th>
|
||||
<th *ngFor="let ev of events" class="text-light" style="text-align: center;background-color: #545B62">{{ev.start | date:'EEEE d LLLL': 'CEST':'fr'}}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th *ngFor="let ev of events" style="text-align: center">{{ev.start | date:'H:mm'}} <BR>-<BR> {{ev.end | date:'H:mm'}}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><span>{{uniqueUsers.length}} participant</span><span *ngIf="uniqueUsers.length > 1">s</span></th>
|
||||
<th *ngFor="let pc of poll?.pollChoices" style="text-align: center">{{pc.users.length}}</th>
|
||||
</tr>
|
||||
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let u of userChoices | keyvalue">
|
||||
<td><input type="text" [disabled]='true' pInputText [ngModel]="uniqueUsers | usernamePipe:u.key"></td>
|
||||
<td *ngFor="let ev of events" style="text-align: center"><p-checkbox [disabled]='true' [binary]="true" [ngModel]="u.value | selecteddate4userPipe:u.key:ev" ></p-checkbox></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td></td>
|
||||
<td *ngFor="let ev of events" style="text-align: center"><p-button [disabled]="poll?.clos" (onClick)="selectEvent($event,ev )" >sélectionner cette date</p-button></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</ng-template>
|
||||
<ng-template pTemplate="footer">
|
||||
|
||||
<app-show-comments *ngIf="poll" [comments]="comments"></app-show-comments>
|
||||
|
||||
</ng-template>
|
||||
</p-card>
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { AdminPollComponent } from './admin-poll.component';
|
||||
|
||||
describe('AdminPollComponent', () => {
|
||||
let component: AdminPollComponent;
|
||||
let fixture: ComponentFixture<AdminPollComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ AdminPollComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(AdminPollComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,95 @@
|
||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||
import { MessageService } from 'primeng/api';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { PollService } from '../poll-service.service';
|
||||
import { Poll, User, PollChoice, PollCommentElement, ChoiceUser } from '../model/model';
|
||||
import { CalendarOptions, EventInput } from '@fullcalendar/core';
|
||||
import { FullCalendarComponent } from '@fullcalendar/angular';
|
||||
|
||||
@Component({
|
||||
selector: 'app-admin-poll',
|
||||
templateUrl: './admin-poll.component.html',
|
||||
styleUrls: ['./admin-poll.component.css'],
|
||||
providers: [MessageService, PollService, FullCalendarComponent]
|
||||
|
||||
})
|
||||
export class AdminPollComponent implements OnInit {
|
||||
|
||||
constructor(public messageService: MessageService, private actRoute: ActivatedRoute, private pollService: PollService) { }
|
||||
slugid: string;
|
||||
poll: Poll;
|
||||
events: EventInput[] = [];
|
||||
uniqueUsers: User[] = [];
|
||||
userChoices: Map<number, PollChoice[]> = new Map();
|
||||
comments: PollCommentElement[];
|
||||
|
||||
ngOnInit(): void {
|
||||
this.actRoute.paramMap.subscribe(params => {
|
||||
this.slugid = params.get('slugadminid');
|
||||
this.pollService.getPollBySlugAdminId(this.slugid).subscribe(p => {
|
||||
this.poll = p;
|
||||
if (p != null){
|
||||
this.pollService.getComentsBySlugId(this.poll?.slug).subscribe(cs => this.comments = cs);
|
||||
}
|
||||
this.uniqueUsers.splice(0, this.uniqueUsers.length);
|
||||
this.poll.pollChoices.forEach(pc => {
|
||||
pc.users.forEach(user => {
|
||||
if (this.uniqueUsers.filter(us => us.id === user.id).length === 0 ){
|
||||
this.uniqueUsers.push(user);
|
||||
this.userChoices.set(user.id, []);
|
||||
}
|
||||
});
|
||||
|
||||
const evt =
|
||||
{
|
||||
title: '',
|
||||
start: pc.startDate,
|
||||
end: pc.endDate,
|
||||
resourceEditable: false,
|
||||
eventResizableFromStart: false,
|
||||
backgroundColor: 'red',
|
||||
extendedProps: {
|
||||
choiceid: pc.id,
|
||||
selected: false
|
||||
},
|
||||
};
|
||||
this.events.push(evt);
|
||||
});
|
||||
this.poll.pollChoices.forEach(pc => {
|
||||
pc.users.forEach(us => {
|
||||
this.userChoices.get(us.id).push(pc);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
selectEvent($event: any, event: EventInput): void{
|
||||
this.pollService.selectEvent(event.extendedProps.choiceid).subscribe(e => {
|
||||
this.messageService.add({
|
||||
severity: 'success',
|
||||
summary: 'Données enregistrées',
|
||||
detail: 'Le sondage est maintenant close'}
|
||||
);
|
||||
this.poll.clos = true;
|
||||
}, (error) => {
|
||||
this.messageService.add(
|
||||
{
|
||||
severity: 'warn',
|
||||
summary: 'Sélection de cette date impossible',
|
||||
detail: 'Le sondage n\'a pu être clos'}
|
||||
);
|
||||
});
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,488 @@
|
||||
:root {
|
||||
--header-height : 180px;
|
||||
--participant-width : 230px;
|
||||
--cell-width : 65px;
|
||||
--cell-height: 40px;
|
||||
--new-participant-height : 65px;
|
||||
--cell-padding : 1rem;
|
||||
|
||||
--color-new-participant : #E6E6FF;
|
||||
--color-vote-yes : #E6E6FF;
|
||||
--color-vote-no :rgb(254,246,246) ;
|
||||
}
|
||||
|
||||
ul{
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
|
||||
.Poll_Has_Meal {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.Poll_Vote_Wrapper{
|
||||
display: flex;
|
||||
justify-content:center;
|
||||
}
|
||||
|
||||
.Poll_Vote_Content{
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
/*border : 2px solid black; */
|
||||
max-width: 100%;
|
||||
max-height: 390px;
|
||||
overflow-y: scroll;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.Cell_Poll_Header{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.Cell_Option .Cell_Poll_Header, .Cell_Option_Votes{
|
||||
border-left: 1px solid black;
|
||||
}
|
||||
|
||||
|
||||
.Cell_Options{
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
.Cell_Option{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/*Tailles de cellules*/
|
||||
.Cell_Participants_Header,.Cell_Participant_Count,
|
||||
.Cell_New_Participant,.Cell_Participant{
|
||||
width: var(--participant-width);
|
||||
}
|
||||
.Cell_Option_Name,.Cell_Option_Count,
|
||||
.Cell_Option_New_Participant_Vote,.Cell_Option_Vote_Yes,
|
||||
.Cell_Option_Vote_No{
|
||||
width: var(--cell-width);
|
||||
}
|
||||
/*.Cell_New_Participant,.Cell_Option_New_Participant_Vote{
|
||||
height: var(--new-participant-height);
|
||||
}*/
|
||||
|
||||
.Cell_Option_New_Participant_Vote {
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.Checkbox_Btn.LastCheck {
|
||||
border-radius: 0 0 5px 0;
|
||||
}
|
||||
|
||||
|
||||
.Cell_Participants_Header,.Cell_Option_Name, .Cell_Header_Name{
|
||||
height: var(--header-height);
|
||||
}
|
||||
/*.Cell_Participant_Count,.Cell_Option_Count,
|
||||
.Cell_Option_Vote_Yes,.Cell_Option_Vote_No,.Cell_Participant{
|
||||
height: var(--cell-height);
|
||||
}*/
|
||||
|
||||
.Cell_Participant_Count {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
|
||||
/* Disposition dans les cellules */
|
||||
.Cell_Participants_Header,.Cell_Participant_Count,
|
||||
.Cell_Option_Count,
|
||||
.Cell_Option_Vote_Yes,.Cell_Option_Vote_No{
|
||||
padding: var(--cell-padding);
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
/*border: 1px solid black;*/
|
||||
}
|
||||
|
||||
.Poll_Vote_Content {
|
||||
border: 1px solid #E6E6FF;
|
||||
}
|
||||
|
||||
/*.Cell_Participant,.Cell_New_Participant{
|
||||
padding: var(--cell-padding);
|
||||
padding-left: 20%;
|
||||
border: 1px solid black;
|
||||
text-align: left;
|
||||
}*/
|
||||
|
||||
.Cell_Option_Name, .Cell_Participants_Header {
|
||||
border-bottom: 1px solid black;
|
||||
}
|
||||
|
||||
|
||||
.Cell_Participant, .Cell_Vote {
|
||||
height: 50px;
|
||||
padding: 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
|
||||
/*Couleurs*/
|
||||
.Cell_Option_New_Participant_Vote{
|
||||
background-color: var(--color-new-participant);
|
||||
}
|
||||
.Cell_Option_Vote_Yes{
|
||||
background-color: var(--color-vote-yes)
|
||||
}
|
||||
.Cell_Option_Vote_No{
|
||||
background-color: var(--color-vote-no)
|
||||
}
|
||||
/* Fixe la premiere colonne et parametrage du scroll*/
|
||||
|
||||
.Cell_Options{
|
||||
overflow-x: scroll;
|
||||
}
|
||||
|
||||
|
||||
.Cell_New_Participant_Input {
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
border-radius: 0;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.Cell_New_Participant_Input.error {
|
||||
border: 2px solid #EF4E4E;
|
||||
}
|
||||
|
||||
.Poll_View_Btn {
|
||||
display: flex;
|
||||
flex: 1 1;
|
||||
border: 1px solid #1D0EBE;
|
||||
display: flex;
|
||||
color: #4D3DF7;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
padding: 0.5rem 1.2rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.Poll_View_Btn:first-child {
|
||||
border-radius: 5px 0 0 5px;
|
||||
}
|
||||
|
||||
.Poll_View_Btn:last-child {
|
||||
border-radius: 0 5px 5px 0;
|
||||
}
|
||||
|
||||
.Poll_View_Btn.active {
|
||||
background-color: #4D3DF7;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.Poll_Btns {
|
||||
display: flex;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.Meal_Preferences_Toggle {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 1rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.Poll_Location {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.feather {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.Cell_Header {
|
||||
width: var(--participant-width);
|
||||
}
|
||||
|
||||
.Poll_Has_Meal {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.Poll_Description_Title {
|
||||
font-size: 0.8rem;
|
||||
color: #243B53;
|
||||
}
|
||||
|
||||
.Poll_Infos {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.Poll_Info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.green {
|
||||
background-color: #199473;
|
||||
}
|
||||
|
||||
.green:hover {
|
||||
background-color: #147D64;
|
||||
}
|
||||
|
||||
.Poll_Vote_Action {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
|
||||
.Cell_Poll_Header.Cell_Option_Name {
|
||||
width: 100%!important;
|
||||
}
|
||||
|
||||
.selected {
|
||||
background-color: #65D6AD;
|
||||
color: #014D40;
|
||||
}
|
||||
|
||||
.Poll_Subtitle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
font-size: 1rem;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.Dates {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 0.8rem;
|
||||
color: grey;
|
||||
}
|
||||
|
||||
.Pad_Url {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.Edit_Link {
|
||||
margin-left: 1rem;
|
||||
font-weight: 500;
|
||||
color: #4d3cf7;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.Edit_Link:hover {
|
||||
color: #1D0EBE;
|
||||
}
|
||||
|
||||
.Link {
|
||||
text-decoration: none;
|
||||
color: #4d3cf7;
|
||||
}
|
||||
|
||||
.Link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.Cell_Day {
|
||||
background-color: #4d3cf7;
|
||||
color: white;
|
||||
padding: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.Poll_Start_Date {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.Poll_Date {
|
||||
text-align: center;
|
||||
padding: 8px;
|
||||
color: #4d3cf7;
|
||||
}
|
||||
|
||||
.Checkbox_Btn {
|
||||
width: 65px;
|
||||
height: 50px;
|
||||
border: none;
|
||||
outline: none;
|
||||
text-align: center;
|
||||
transition: background-color 0.3s linear;
|
||||
border: 1px solid #4d3cf7;
|
||||
}
|
||||
|
||||
.Checkbox_Btn.Active {
|
||||
background-color: #4d3cf7;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.Cell_New_Participant {
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
|
||||
.Links {
|
||||
display: flex;
|
||||
font-weight: 600;
|
||||
width: 100%;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
background-color: #4D3DF7;
|
||||
border-radius: 5px 5px 0 0;
|
||||
box-shadow: 0 2px 4px 0 rgba(0,0,0,0.10);
|
||||
|
||||
}
|
||||
|
||||
.Links_Right {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.Links_Left {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.Feat_Link {
|
||||
padding: 0.7rem 1rem;
|
||||
text-decoration: none;
|
||||
color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
transition: all 0.3s linear;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.Feat_Link:last-child {
|
||||
border-radius: 0 5px 0 0;
|
||||
}
|
||||
|
||||
.Feat_Link.Unique {
|
||||
border-radius: 5px 5px 0 0!important;
|
||||
}
|
||||
|
||||
.Feat_Link:hover {
|
||||
background-color: #0C008C;
|
||||
}
|
||||
|
||||
.MealPref {
|
||||
padding: 1rem 0;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.Author_MealPref {
|
||||
font-weight: bold;
|
||||
color: #4d3cf7;
|
||||
}
|
||||
|
||||
.Author_Comment {
|
||||
font-weight: bold;
|
||||
color: #4d3cf7;
|
||||
}
|
||||
|
||||
|
||||
.orange {
|
||||
background-color: #F7D070;
|
||||
}
|
||||
|
||||
.orange:hover {
|
||||
background-color: #E9B949;
|
||||
}
|
||||
|
||||
.modal {
|
||||
position: fixed; /* Stay in place */
|
||||
z-index: 1; /* Sit on top */
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%; /* Full width */
|
||||
height: 100%; /* Full height */
|
||||
overflow: auto; /* Enable scroll if needed */
|
||||
background-color: rgb(0,0,0); /* Fallback color */
|
||||
background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
|
||||
}
|
||||
|
||||
.Export_Modal {
|
||||
background-color: #fefefe;
|
||||
margin: 15% auto; /* 15% from the top and centered */
|
||||
width: 500px; /* Could be more or less, depending on screen size */
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 160px;
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 2px 4px 0 rgba(0,0,0,0.10);
|
||||
}
|
||||
|
||||
.Export {
|
||||
padding: 20px;
|
||||
height: 100%;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
color: #4d3cf7;
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
transition: background-color 0.3s linear;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.Export:hover {
|
||||
background-color: #4d3cf7;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.Export:first-child {
|
||||
border-right: 4px solid #4d3cf7;
|
||||
border-radius: 3px 0 0 3px;
|
||||
}
|
||||
|
||||
.Export:last-child {
|
||||
border-radius: 0 3px 3px 0;
|
||||
}
|
||||
|
||||
.Export_Icon {
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
.Poll_Informations {
|
||||
background-color: white;
|
||||
width: 100%;
|
||||
margin-top: 1rem;
|
||||
box-shadow: 0 2px 4px 0 rgba(0,0,0,0.10);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.Meal_Preferences {
|
||||
padding: 1rem;
|
||||
border-bottom: 2px solid #F0F4F8;
|
||||
}
|
||||
|
||||
.Disabled {
|
||||
background-color: #cccccc;
|
||||
}
|
||||
|
||||
.Disabled:hover {
|
||||
color: #4d3cf7;
|
||||
background-color: #cccccc;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.Comment {
|
||||
padding: 1rem;
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
<div class="Container">
|
||||
<img src="../../assets/flat_logo.png" alt="Logo Simba" height="50px" [ngStyle]="{ 'marginBottom': '1rem' }" />
|
||||
<app-top-bar [slug]="poll?.slug" [padURL]="poll?.padURL" [talkToURL]="poll?.tlkURL" ></app-top-bar>
|
||||
|
||||
|
||||
<p-card>
|
||||
<p-toast></p-toast>
|
||||
<ng-template pTemplate="title">
|
||||
<h1>{{poll?.title}}</h1>
|
||||
</ng-template>
|
||||
<ng-template pTemplate="subtitle">
|
||||
<div class="Dates"><span>Créé il y a {{poll?.createdAt | dateago}}</span></div>
|
||||
</ng-template>
|
||||
<ng-template pTemplate="content">
|
||||
<div class="Poll_Infos">
|
||||
<p class="Poll_Location"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24"
|
||||
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||||
class="feather feather-map-pin">
|
||||
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path>
|
||||
<circle cx="12" cy="10" r="3"></circle>
|
||||
</svg>{{poll?.location}}</p>
|
||||
<div *ngIf="poll?.has_meal" class="Poll_Has_Meal"><svg class="feather" aria-hidden="true" width="20" height="20"
|
||||
focusable="false" data-prefix="fas" data-icon="utensils" role="img" xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 416 512">
|
||||
<path fill="currentColor"
|
||||
d="M207.9 15.2c.8 4.7 16.1 94.5 16.1 128.8 0 52.3-27.8 89.6-68.9 104.6L168 486.7c.7 13.7-10.2 25.3-24 25.3H80c-13.7 0-24.7-11.5-24-25.3l12.9-238.1C27.7 233.6 0 196.2 0 144 0 109.6 15.3 19.9 16.1 15.2 19.3-5.1 61.4-5.4 64 16.3v141.2c1.3 3.4 15.1 3.2 16 0 1.4-25.3 7.9-139.2 8-141.8 3.3-20.8 44.7-20.8 47.9 0 .2 2.7 6.6 116.5 8 141.8.9 3.2 14.8 3.4 16 0V16.3c2.6-21.6 44.8-21.4 48-1.1zm119.2 285.7l-15 185.1c-1.2 14 9.9 26 23.9 26h56c13.3 0 24-10.7 24-24V24c0-13.2-10.7-24-24-24-82.5 0-221.4 178.5-64.9 300.9z">
|
||||
</path>
|
||||
</svg>
|
||||
Cet évènement contient un repas
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="p-fluid">
|
||||
<div class="p-field">
|
||||
<label for="nom">Nom prénom participant</label>
|
||||
<input #nom="ngModel" id="nom" type="text" required pInputText [(ngModel)]="personalInformation.nom"
|
||||
[ngClass]="{'p-invalid': (nom.invalid && submitted) || (nom.dirty && nom.invalid)}">
|
||||
<small *ngIf="(nom.invalid && submitted) || (nom.dirty && nom.invalid)" class="p-error">Le nom est
|
||||
requis.</small>
|
||||
</div>
|
||||
<div class="p-field">
|
||||
<label for="mail">Email participant</label>
|
||||
<span class="p-input-icon-right">
|
||||
<i *ngIf="loademail" class="pi pi-spin pi-spinner" ></i>
|
||||
|
||||
<input #mail="ngModel" id="mail" type="email" required pInputText (change)="getUserFromMail()" [(ngModel)]="personalInformation.mail"
|
||||
[ngClass]="{'p-invalid': (mail.invalid && submitted) || (mail.dirty && mail.invalid)}">
|
||||
</span>
|
||||
<small class="p-error" *ngIf="(mail.invalid && submitted )|| (mail.dirty && mail.invalid)">Le
|
||||
mail est requis.</small>
|
||||
|
||||
</div>
|
||||
<div class="p-field">
|
||||
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 1rem;font-weight: normal;">Avez vous un agenda avec un flux ics accessible ?</p>
|
||||
<p-inputSwitch [(ngModel)]="hasics"></p-inputSwitch>
|
||||
</div>
|
||||
<div *ngIf="hasics" class="p-field">
|
||||
<label for="ics">URL ICS du participant</label>
|
||||
<span class="p-input-icon-right">
|
||||
<i *ngIf="loadics" class="pi pi-spin pi-spinner" ></i>
|
||||
<input #mail="ngModel" id="ics" type="text" pInputText (change)="getICS()" [(ngModel)]="personalInformation.ics">
|
||||
</span>
|
||||
</div>
|
||||
<div *ngIf="poll?.has_meal" class="p-field">
|
||||
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 1rem;font-weight: normal;">Avez vous des préférences alimentaires ?</p>
|
||||
<p-inputSwitch [(ngModel)]="personalInformation.pref"></p-inputSwitch>
|
||||
</div>
|
||||
<div *ngIf="personalInformation?.pref" class="p-field">
|
||||
<label for="desc">Description préférences alimentaires</label>
|
||||
<textarea #desc="ngModel" id="desc" required pInputTextarea [(ngModel)]="personalInformation.desc"
|
||||
[ngClass]="{'p-invalid': (desc.invalid && submitted) || (desc.dirty && desc.invalid)}"></textarea>
|
||||
<small class="p-error" *ngIf="(desc.invalid && submitted) || (desc.dirty && desc.invalid)">La description est
|
||||
requise.</small>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p-selectButton [options]="calendarortableoption" [(ngModel)]="calendarortable">
|
||||
<ng-template let-item>
|
||||
<i [class]="item.icon">Vue {{item.text}}</i>
|
||||
</ng-template>
|
||||
</p-selectButton>
|
||||
</div>
|
||||
|
||||
<div *ngIf="calendarortable ==='calendar'">
|
||||
<full-calendar #calendar [options]="options"></full-calendar>
|
||||
</div>
|
||||
|
||||
<div *ngIf="calendarortable !='calendar'">
|
||||
<div class="container-fluid">
|
||||
<div class="table-responsive-sm card">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th rowspan="2"></th>
|
||||
<th *ngFor="let ev of events" class="text-light" style="text-align: center;background-color: #545B62">{{ev.start | date:'EEEE d LLLL': 'CEST':'fr'}}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th *ngFor="let ev of events" style="text-align: center">{{ev.start | date:'H:mm'}} <BR>-<BR> {{ev.end | date:'H:mm'}}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><span>{{uniqueUsers.length + 1}} participant</span><span *ngIf="uniqueUsers.length > 0">s</span></th>
|
||||
<th *ngFor="let pc of poll?.pollChoices" style="text-align: center">{{pc.users.length}}</th>
|
||||
</tr>
|
||||
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let u of userChoices | keyvalue">
|
||||
<td><input type="text" [disabled]='true' pInputText [ngModel]="uniqueUsers | usernamePipe:u.key"></td>
|
||||
<td *ngFor="let ev of events" style="text-align: center"><p-checkbox [disabled]='true' [binary]="true" [ngModel]="u.value | selecteddate4userPipe:u.key:ev" ></p-checkbox></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td><input #nomvotant="" id="nomvotant" type="text" required pInputText [(ngModel)]="personalInformation.nom"></td>
|
||||
<td *ngFor="let ev of events" style="text-align: center"><p-checkbox [binary]="true" (onChange)="updateEvent($event,ev )" [ngModel]="ev.extendedProps.selected" ></p-checkbox></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p-button [disabled]="voeuxsoumis" label="Soumettre voeux" (onClick)="createReponse()"
|
||||
icon="pi pi-angle-left"></p-button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</ng-template>
|
||||
<ng-template pTemplate="footer">
|
||||
<app-show-comments *ngIf="poll" [comments]="comments"></app-show-comments>
|
||||
|
||||
<div class="p-fluid">
|
||||
<div class="p-field">
|
||||
<label for="comment">Auteur du commentaire associé à ce sondage</label>
|
||||
<input #comment="ngModel" id="comment" type="text" required pInputText [(ngModel)]="comment1"
|
||||
[ngClass]="{'p-invalid': (comment.invalid && csubmitted) || (comment.dirty && comment.invalid)}">
|
||||
<small *ngIf="(comment.invalid && csubmitted) || (comment.dirty && comment.invalid)" class="p-error">L'auteur
|
||||
du commentaire est requis.</small>
|
||||
</div>
|
||||
|
||||
<div class="p-field">
|
||||
<label for="commentdesc">Commentaire</label>
|
||||
<textarea #commentdesc="ngModel" id="commentdesc" required pInputTextarea [(ngModel)]="commentdesc1"
|
||||
[ngClass]="{'p-invalid': (commentdesc.invalid && csubmitted) || (commentdesc.dirty && commentdesc.invalid)}"></textarea>
|
||||
<small class="p-error"
|
||||
*ngIf="(commentdesc.invalid && csubmitted) || (commentdesc.dirty && commentdesc.invalid)">Le commentaire est
|
||||
requis.</small>
|
||||
</div>
|
||||
<div>
|
||||
<p-button [disabled]="commentsoumis" label="Ajouter commentaire" (onClick)="createComment()"
|
||||
icon="pi pi-angle-left"></p-button>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</ng-template>
|
||||
</p-card>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<!--<p-button (onClick)="testEvent()">test</p-button>-->
|
||||
<!-- Modal -->
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { AnswerPollComponent } from './answer-poll.component';
|
||||
|
||||
describe('AnswerPollComponent', () => {
|
||||
let component: AnswerPollComponent;
|
||||
let fixture: ComponentFixture<AnswerPollComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ AnswerPollComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(AnswerPollComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,360 @@
|
||||
import { Component, OnInit, ViewChild, AfterViewChecked } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { PollService } from '../poll-service.service';
|
||||
import { Poll, ChoiceUser, PollCommentElement, User, PollChoice } from '../model/model';
|
||||
import { CalendarOptions, EventInput } from '@fullcalendar/core';
|
||||
import { FullCalendarComponent } from '@fullcalendar/angular';
|
||||
import frLocale from '@fullcalendar/core/locales/fr';
|
||||
import { MessageService } from 'primeng/api';
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { ModalPollClosComponent } from '../modal-poll-clos/modal-poll-clos.component';
|
||||
import dayGridPlugin from '@fullcalendar/daygrid';
|
||||
import interactionPlugin from '@fullcalendar/interaction';
|
||||
import timeGridPlugin from '@fullcalendar/timegrid';
|
||||
|
||||
@Component({
|
||||
selector: 'app-answer-poll',
|
||||
templateUrl: './answer-poll.component.html',
|
||||
styleUrls: ['./answer-poll.component.css'],
|
||||
providers: [MessageService, PollService, FullCalendarComponent, NgbModal]
|
||||
|
||||
})
|
||||
export class AnswerPollComponent implements OnInit {
|
||||
|
||||
constructor(public messageService: MessageService,
|
||||
// tslint:disable-next-line:align
|
||||
private actRoute: ActivatedRoute, private pollService: PollService,
|
||||
// tslint:disable-next-line:align
|
||||
private modalService: NgbModal) { }
|
||||
slugid: string;
|
||||
poll: Poll;
|
||||
calendarortableoption: any[];
|
||||
calendarortable = 'calendar';
|
||||
personalInformation: any = {
|
||||
nom: '',
|
||||
mail: '',
|
||||
desc: '',
|
||||
ics: '',
|
||||
pref: false
|
||||
};
|
||||
hasics: false;
|
||||
options: CalendarOptions;
|
||||
@ViewChild('calendar') calendarComponent: FullCalendarComponent;
|
||||
submitted = false;
|
||||
csubmitted = false;
|
||||
voeuxsoumis = false;
|
||||
commentsoumis = false;
|
||||
events: EventInput[] = [];
|
||||
eventsfromics: EventInput[] = [];
|
||||
allevents: EventInput[] = [];
|
||||
loadics = false;
|
||||
loademail = false;
|
||||
comments: PollCommentElement[];
|
||||
|
||||
comment1 = '';
|
||||
commentdesc1 = '';
|
||||
uniqueUsers: User[] = [];
|
||||
userChoices: Map<number, PollChoice[]> = new Map();
|
||||
ngOnInit(): void {
|
||||
this.calendarortableoption = [
|
||||
{ icon: 'pi pi-calendar', text: 'Calendrier', value: 'calendar' },
|
||||
{ icon: 'pi pi-table', text: 'Tableau', value: 'table' },
|
||||
];
|
||||
|
||||
|
||||
this.actRoute.paramMap.subscribe(params => {
|
||||
this.slugid = params.get('slugid');
|
||||
this.pollService.getPollBySlugId(this.slugid).subscribe(p => {
|
||||
this.poll = p;
|
||||
this.pollService.getComentsBySlugId(this.slugid).subscribe(cs => this.comments = cs);
|
||||
|
||||
|
||||
if (this.poll.clos) {
|
||||
this.openModal();
|
||||
}
|
||||
const calendarApi = this.calendarComponent.getApi();
|
||||
// calendarApi.next();
|
||||
this.uniqueUsers.splice(0, this.uniqueUsers.length);
|
||||
this.poll.pollChoices.forEach(pc => {
|
||||
pc.users.forEach(user => {
|
||||
if (this.uniqueUsers.filter(us => us.id === user.id).length === 0) {
|
||||
this.uniqueUsers.push(user);
|
||||
this.userChoices.set(user.id, []);
|
||||
}
|
||||
});
|
||||
|
||||
const evt =
|
||||
{
|
||||
title: '',
|
||||
start: pc.startDate,
|
||||
end: pc.endDate,
|
||||
resourceEditable: false,
|
||||
eventResizableFromStart: false,
|
||||
backgroundColor: 'red',
|
||||
id: this.getUniqueId(8),
|
||||
extendedProps: {
|
||||
choiceid: pc.id,
|
||||
selected: false,
|
||||
},
|
||||
};
|
||||
calendarApi.addEvent(evt, true);
|
||||
this.events.push(evt);
|
||||
this.allevents.push(evt);
|
||||
|
||||
});
|
||||
this.poll.pollChoices.forEach(pc => {
|
||||
pc.users.forEach(us => {
|
||||
this.userChoices.get(us.id).push(pc);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
this.options = {
|
||||
initialView: 'timeGridWeek',
|
||||
plugins: [dayGridPlugin, interactionPlugin, timeGridPlugin],
|
||||
|
||||
// dateClick: this.handleDateClick.bind(this), // bind is important!
|
||||
/*eventDragStart: (timeSheetEntry, jsEvent, ui, activeView) => {
|
||||
this.eventDragStart(
|
||||
timeSheetEntry, jsEvent, ui, activeView
|
||||
);
|
||||
},
|
||||
eventDragStop: (timeSheetEntry, jsEvent, ui, activeView) => {
|
||||
this.eventDragStop(
|
||||
timeSheetEntry, jsEvent, ui, activeView
|
||||
);
|
||||
},*/
|
||||
// events: this.events,
|
||||
events: this.allevents,
|
||||
editable: false,
|
||||
droppable: false,
|
||||
// selectMirror: true,
|
||||
eventResizableFromStart: false,
|
||||
selectable: false,
|
||||
locale: frLocale,
|
||||
themeSystem: 'bootstrap',
|
||||
slotMinTime: '08:00:00',
|
||||
slotMaxTime: '20:00:00',
|
||||
eventMouseEnter: (mouseEnterInfo) => {
|
||||
|
||||
},
|
||||
eventClick: (info) => {
|
||||
if (!info.event.extendedProps.fromics) {
|
||||
if (info.event.extendedProps.selected) {
|
||||
info.event.setExtendedProp('selected', false);
|
||||
const evt = this.events.filter(e => e.extendedProps.choiceid === info.event.extendedProps.choiceid).pop();
|
||||
evt.extendedProps.selected = false;
|
||||
evt.backgroundColor = 'red';
|
||||
info.event.setProp('backgroundColor', 'red');
|
||||
this.poll.pollChoices.filter(pc => pc.id === evt.extendedProps.choiceid)[0].users.splice(-1, 1);
|
||||
|
||||
} else {
|
||||
info.event.setExtendedProp('selected', true);
|
||||
const evt = this.events.filter(e => e.extendedProps.choiceid === info.event.extendedProps.choiceid).pop();
|
||||
evt.extendedProps.selected = true;
|
||||
evt.backgroundColor = 'green';
|
||||
info.event.setProp('backgroundColor', 'green');
|
||||
this.poll.pollChoices.filter(pc => pc.id === evt.extendedProps.choiceid)[0].users.push({ id: -1 });
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// info.event.remove();
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
updateEvent($event: any, event: EventInput): void {
|
||||
|
||||
event.extendedProps.selected = $event.checked;
|
||||
if ($event.checked) {
|
||||
event.backgroundColor = 'green';
|
||||
this.poll.pollChoices.filter(pc => pc.id === event.extendedProps.choiceid)[0].users.push({ id: -1 });
|
||||
|
||||
} else {
|
||||
event.backgroundColor = 'red';
|
||||
this.poll.pollChoices.filter(pc => pc.id === event.extendedProps.choiceid)[0].users.splice(-1, 1);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
createComment(): void {
|
||||
|
||||
|
||||
if (this.comment1 && this.commentdesc1) {
|
||||
const c: PollCommentElement = {
|
||||
content: this.commentdesc1,
|
||||
auteur: this.comment1
|
||||
};
|
||||
this.pollService.addComment4Poll(this.slugid, c).subscribe(e => {
|
||||
this.messageService.add({
|
||||
severity: 'success',
|
||||
summary: 'Données enregistrées',
|
||||
detail: 'Merci pour ce commentaire'
|
||||
}
|
||||
);
|
||||
this.pollService.getComentsBySlugId(this.poll?.slug).subscribe(cs => this.comments = cs);
|
||||
this.commentsoumis = true;
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
this.messageService.add(
|
||||
{
|
||||
severity: 'warn',
|
||||
summary: 'Données incomplètes',
|
||||
detail: 'Veuillez remplir les champs requis'
|
||||
}
|
||||
);
|
||||
this.csubmitted = true;
|
||||
}
|
||||
|
||||
createReponse(): void {
|
||||
if (this.personalInformation.nom && this.personalInformation.mail &&
|
||||
this.events.filter(e => e.extendedProps.selected).length > 0 &&
|
||||
(this.personalInformation.desc || !this.personalInformation.pref)) {
|
||||
const cu: ChoiceUser = {
|
||||
username: this.personalInformation.nom,
|
||||
mail: this.personalInformation.mail,
|
||||
pref: this.personalInformation.desc,
|
||||
ics: this.personalInformation.ics,
|
||||
choices: this.events.filter(e => e.extendedProps.selected).map(x => x.extendedProps.choiceid)
|
||||
};
|
||||
this.pollService.updateChoice4user(cu).subscribe(e => {
|
||||
// cu.choices.forEach(c => this.poll.pollChoices.filter( c1 => c1.id === c)[0].users.push(e));
|
||||
// if (this.uniqueUsers.filter(u1 => u1.id === e.id ).length === 0) {
|
||||
// this.uniqueUsers.push(e);
|
||||
// }
|
||||
this.messageService.add({
|
||||
severity: 'success',
|
||||
summary: 'Données enregistrées',
|
||||
detail: 'Merci pour votre participation'
|
||||
}
|
||||
);
|
||||
this.voeuxsoumis = true;
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.messageService.add(
|
||||
{
|
||||
severity: 'warn',
|
||||
summary: 'Données incomplètes',
|
||||
detail: 'Veuillez remplir les champs requis et sélectioner au moins une date'
|
||||
}
|
||||
);
|
||||
this.submitted = true;
|
||||
|
||||
}
|
||||
|
||||
getICS(): void {
|
||||
this.loadics = true;
|
||||
this.pollService.getICS(this.slugid, this.personalInformation.ics).subscribe(res => {
|
||||
this.loadics = false;
|
||||
|
||||
const calendarApi = this.calendarComponent.getApi();
|
||||
if (res.eventdtos.length > 0) {
|
||||
this.eventsfromics.forEach(eid => {
|
||||
const index = this.allevents.indexOf(eid);
|
||||
if (index > -1) {
|
||||
this.allevents.splice(index, 1);
|
||||
}
|
||||
calendarApi.getEventById(eid.id)?.remove();
|
||||
});
|
||||
this.eventsfromics = [];
|
||||
}
|
||||
console.log(res);
|
||||
|
||||
res.eventdtos.forEach(evtdto => { // calendarApi.next();
|
||||
const evt1 =
|
||||
{
|
||||
title: evtdto.description,
|
||||
start: evtdto.startDate,
|
||||
end: evtdto.endDate,
|
||||
resourceEditable: false,
|
||||
eventResizableFromStart: false,
|
||||
id: this.getUniqueId(8),
|
||||
|
||||
backgroundColor: 'blue',
|
||||
extendedProps: {
|
||||
fromics: true
|
||||
},
|
||||
|
||||
|
||||
};
|
||||
const eventAPI = calendarApi.addEvent(evt1, true);
|
||||
this.eventsfromics.push(evt1);
|
||||
this.allevents.push(evt1);
|
||||
|
||||
});
|
||||
|
||||
const unselected = this.events.map(ev => ev.extendedProps.choiceid);
|
||||
res.selectedChoices.forEach(e => {
|
||||
const index = unselected.indexOf(e);
|
||||
if (index > -1) {
|
||||
unselected.splice(index, 1);
|
||||
}
|
||||
const evt1 = this.events.filter(ev => ev.extendedProps.choiceid === e)[0];
|
||||
|
||||
const evt2 = calendarApi.getEventById(evt1.id);
|
||||
evt1.backgroundColor = 'red';
|
||||
evt1.extendedProps.selected = false;
|
||||
evt2.setProp('backgroundColor', 'red');
|
||||
// this.poll.pollChoices.filter(pc => pc.id === evt1.extendedProps.choiceid)[0].users.push({ id: -1 });
|
||||
});
|
||||
unselected.forEach(e => {
|
||||
const evt1 = this.events.filter(ev => ev.extendedProps.choiceid === e)[0];
|
||||
|
||||
const evt2 = calendarApi.getEventById(evt1.id);
|
||||
evt1.backgroundColor = 'green';
|
||||
evt1.extendedProps.selected = true;
|
||||
evt2.setProp('backgroundColor', 'green');
|
||||
this.poll.pollChoices.filter(pc => pc.id === evt1.extendedProps.choiceid)[0].users.push({ id: -1 });
|
||||
});
|
||||
}, (err) => {
|
||||
this.loadics = false;
|
||||
|
||||
this.messageService.add(
|
||||
{
|
||||
severity: 'warn',
|
||||
summary: 'Ne peut récupérer l\'agenda à partir de l\'adresse de l\'ics',
|
||||
detail: 'Une erreur s\'est produite au moment de la récupération de l\'agenda'
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
openModal(): void {
|
||||
const modalRef = this.modalService.open(ModalPollClosComponent, {
|
||||
beforeDismiss: () => false,
|
||||
centered: true,
|
||||
windowClass: 'lgModal',
|
||||
backdrop: 'static'
|
||||
});
|
||||
modalRef.componentInstance.poll = this.poll;
|
||||
}
|
||||
|
||||
getUserFromMail(): void {
|
||||
}
|
||||
|
||||
private getUniqueId(parts: number): string {
|
||||
const stringArr = [];
|
||||
for (let i = 0; i < parts; i++) {
|
||||
// tslint:disable-next-line:no-bitwise
|
||||
const S4 = (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
|
||||
stringArr.push(S4);
|
||||
}
|
||||
return stringArr.join('-');
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { Routes, RouterModule } from '@angular/router';
|
||||
import { HomeComponentComponent } from './home-component/home-component.component';
|
||||
import { CreatePollComponentComponent } from './create-poll-component/create-poll-component.component';
|
||||
import { AnswerPollComponent } from './answer-poll/answer-poll.component';
|
||||
import { AdminPollComponent } from './admin-poll/admin-poll.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: HomeComponentComponent
|
||||
},
|
||||
{
|
||||
path: 'create',
|
||||
component: CreatePollComponentComponent
|
||||
},
|
||||
{
|
||||
path: 'update/:slugadminid',
|
||||
component: CreatePollComponentComponent
|
||||
},
|
||||
{
|
||||
path: 'answer/:slugid',
|
||||
component: AnswerPollComponent
|
||||
},
|
||||
{
|
||||
path: 'admin/:slugadminid',
|
||||
component: AdminPollComponent
|
||||
}
|
||||
|
||||
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forRoot(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class AppRoutingModule { }
|
||||
87
ansible/files/doodlestudent/front/src/app/app.component.css
Normal file
87
ansible/files/doodlestudent/front/src/app/app.component.css
Normal file
@@ -0,0 +1,87 @@
|
||||
.Container {
|
||||
max-width: 800px;
|
||||
margin: 2rem auto;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.SmallCard_Container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.SmallCard {
|
||||
background-color: white;
|
||||
margin-right: 1rem;
|
||||
padding: 2rem;
|
||||
border-radius: 0.5rem;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.SmallCard_Image {
|
||||
padding: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.SmallCard_Title {
|
||||
padding: 1rem;
|
||||
font-weight: 800;
|
||||
text-align: center;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.SmallCard_Subtitle {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.SmallCard:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.Home_Container {
|
||||
width: 1024px;
|
||||
margin: 0 auto;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.Home_Wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.Home_Logo {
|
||||
margin-bottom: 2rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.Home_Button {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.Home_CreateLink {
|
||||
padding: 1rem;
|
||||
width: 100%;
|
||||
background-color: #43dbac;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
font-weight: 700;
|
||||
font-size: 1.3rem;
|
||||
box-shadow: 0 2px 4px 0 rgba(0,0,0,0.10);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<router-outlet></router-outlet>
|
||||
@@ -0,0 +1,35 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
describe('AppComponent', () => {
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [
|
||||
RouterTestingModule
|
||||
],
|
||||
declarations: [
|
||||
AppComponent
|
||||
],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
it('should create the app', () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
});
|
||||
|
||||
it(`should have as title 'tlcfront'`, () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.componentInstance;
|
||||
expect(app.title).toEqual('tlcfront');
|
||||
});
|
||||
|
||||
it('should render title', () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
fixture.detectChanges();
|
||||
const compiled = fixture.nativeElement;
|
||||
expect(compiled.querySelector('.content span').textContent).toContain('tlcfront app is running!');
|
||||
});
|
||||
});
|
||||
12
ansible/files/doodlestudent/front/src/app/app.component.ts
Normal file
12
ansible/files/doodlestudent/front/src/app/app.component.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { HomeComponentComponent } from './home-component/home-component.component';
|
||||
import { CreatePollComponentComponent } from './create-poll-component/create-poll-component.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.css'],
|
||||
providers: [HomeComponentComponent, CreatePollComponentComponent]
|
||||
})
|
||||
export class AppComponent {
|
||||
}
|
||||
86
ansible/files/doodlestudent/front/src/app/app.module.ts
Normal file
86
ansible/files/doodlestudent/front/src/app/app.module.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
import { AppComponent } from './app.component';
|
||||
import { CardSmallComponentComponent } from './card-small-component/card-small-component.component';
|
||||
import { HomeComponentComponent } from './home-component/home-component.component';
|
||||
import { CreatePollComponentComponent } from './create-poll-component/create-poll-component.component';
|
||||
import {StepsModule} from 'primeng/steps';
|
||||
import {MenuItem} from 'primeng/api';
|
||||
// import {FullCalendarModule} from 'primeng/fullcalendar';
|
||||
import {ToastModule} from 'primeng/toast';
|
||||
import {MessagesModule} from 'primeng/messages';
|
||||
import {MessageModule} from 'primeng/message';
|
||||
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
||||
import {InputTextareaModule} from 'primeng/inputtextarea';
|
||||
import {InputSwitchModule} from 'primeng/inputswitch';
|
||||
import {CardModule} from 'primeng/card';
|
||||
import {ButtonModule} from 'primeng/button';
|
||||
import {InputTextModule} from 'primeng/inputtext';
|
||||
import {SelectButtonModule} from 'primeng/selectbutton';
|
||||
import {MenubarModule} from 'primeng/menubar';
|
||||
import {CheckboxModule} from 'primeng/checkbox';
|
||||
|
||||
import dayGridPlugin from '@fullcalendar/daygrid'; // a plugin
|
||||
import interactionPlugin from '@fullcalendar/interaction'; // a plugin
|
||||
import timeGridPlugin from '@fullcalendar/timegrid';
|
||||
|
||||
import { FullCalendarModule } from '@fullcalendar/angular';
|
||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { AnswerPollComponent } from './answer-poll/answer-poll.component';
|
||||
import { AdminPollComponent } from './admin-poll/admin-poll.component';
|
||||
import { DateagoPipe } from './dateago.pipe'; // the main connector. must go first
|
||||
|
||||
import { registerLocaleData } from '@angular/common';
|
||||
import localeFr from '@angular/common/locales/fr';
|
||||
import { UsernamePipePipe } from './username-pipe.pipe';
|
||||
import { Selecteddate4userPipePipe } from './selecteddate4user-pipe.pipe';
|
||||
import { ModalPollClosComponent } from './modal-poll-clos/modal-poll-clos.component';
|
||||
import { TopBarComponent } from './top-bar/top-bar.component';
|
||||
import { ShowCommentsComponent } from './show-comments/show-comments.component';
|
||||
|
||||
registerLocaleData(localeFr, 'fr');
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AppComponent,
|
||||
CardSmallComponentComponent,
|
||||
HomeComponentComponent,
|
||||
CreatePollComponentComponent,
|
||||
AnswerPollComponent,
|
||||
AdminPollComponent,
|
||||
DateagoPipe,
|
||||
UsernamePipePipe,
|
||||
Selecteddate4userPipePipe,
|
||||
ModalPollClosComponent,
|
||||
TopBarComponent,
|
||||
ShowCommentsComponent
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
FormsModule,
|
||||
HttpClientModule,
|
||||
BrowserAnimationsModule,
|
||||
AppRoutingModule,
|
||||
StepsModule,
|
||||
FullCalendarModule,
|
||||
ToastModule,
|
||||
MessagesModule,
|
||||
MessageModule,
|
||||
InputSwitchModule,
|
||||
CardModule,
|
||||
ButtonModule,
|
||||
InputTextModule,
|
||||
InputTextareaModule,
|
||||
SelectButtonModule,
|
||||
MenubarModule,
|
||||
CheckboxModule,
|
||||
NgbModule
|
||||
],
|
||||
providers: [],
|
||||
bootstrap: [AppComponent],
|
||||
|
||||
|
||||
})
|
||||
export class AppModule { }
|
||||
@@ -0,0 +1,38 @@
|
||||
|
||||
.SmallCard_Container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
.SmallCard {
|
||||
background-color: white;
|
||||
margin-right: 1rem;
|
||||
padding: 2rem;
|
||||
border-radius: 0.5rem;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.SmallCard_Image {
|
||||
padding: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.SmallCard_Title {
|
||||
padding: 1rem;
|
||||
font-weight: 800;
|
||||
text-align: center;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.SmallCard_Subtitle {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.SmallCard:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<div class="SmallCard_Container">
|
||||
<div *ngFor="let card of cards" class="SmallCard" [ngStyle]="card.style">
|
||||
<div class="SmallCard_Image">
|
||||
<img [src]="card.image" height="200px"/>
|
||||
</div>
|
||||
<div class="SmallCard_Title">
|
||||
{{ card.title }}
|
||||
</div>
|
||||
<div class="SmallCard_Subtitle">
|
||||
{{ card.subtitle }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { CardSmallComponentComponent } from './card-small-component.component';
|
||||
|
||||
describe('CardSmallComponentComponent', () => {
|
||||
let component: CardSmallComponentComponent;
|
||||
let fixture: ComponentFixture<CardSmallComponentComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ CardSmallComponentComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(CardSmallComponentComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
import { Component, OnInit, Input } from '@angular/core';
|
||||
import { Card } from '../home-component/Card';
|
||||
|
||||
@Component({
|
||||
selector: 'app-card-small-component',
|
||||
templateUrl: './card-small-component.component.html',
|
||||
styleUrls: ['./card-small-component.component.css']
|
||||
})
|
||||
export class CardSmallComponentComponent implements OnInit {
|
||||
|
||||
|
||||
@Input()
|
||||
cards: Card[];
|
||||
|
||||
constructor() { }
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
.CreatePoll_Form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.CreatePoll_Input {
|
||||
width: 100%;
|
||||
margin-bottom: 1.2rem;
|
||||
}
|
||||
|
||||
.CreatePoll_Input:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
input, textarea {
|
||||
font-weight: 400;
|
||||
outline: none;
|
||||
color: #102A43;
|
||||
width: 100%;
|
||||
font-size: 17px;
|
||||
border-radius: .25rem;
|
||||
background-color: #F0F4F8;
|
||||
border: 1px solid white;
|
||||
}
|
||||
|
||||
input:focus, textarea:focus {
|
||||
border-color: #4D3DF7;
|
||||
color: #102A43;
|
||||
}
|
||||
|
||||
.CreatePoll_Input input, textarea {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.CreatePoll_Input textarea {
|
||||
max-width: 100%;
|
||||
min-width: 100%;
|
||||
min-height: 50px;
|
||||
}
|
||||
|
||||
.CreatePoll_Buttons {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.CreatePoll_Button {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.CreatePoll_LabelError {
|
||||
color: #EF4E4E;
|
||||
}
|
||||
|
||||
.rbc-calendar {
|
||||
height: 500px;
|
||||
}
|
||||
|
||||
.rbc-event {
|
||||
background-color: #4D3DF7;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.rbc-today {
|
||||
background-color: #E6E6FF;
|
||||
}
|
||||
|
||||
.rbc-current-time-indicator {
|
||||
background-color: #4D3DF7;
|
||||
}
|
||||
|
||||
.rbc-slot-selection {
|
||||
background-color: #C4C6FF;
|
||||
}
|
||||
|
||||
.rbc-time-view {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.switch {
|
||||
display: inline-block;
|
||||
height: 34px;
|
||||
position: relative;
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.switch input {
|
||||
display:none;
|
||||
}
|
||||
|
||||
.slider {
|
||||
background-color: #ccc;
|
||||
bottom: 0;
|
||||
cursor: pointer;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
transition: .4s;
|
||||
}
|
||||
|
||||
.slider:before {
|
||||
background-color: #fff;
|
||||
bottom: 4px;
|
||||
content: "";
|
||||
height: 26px;
|
||||
left: 4px;
|
||||
position: absolute;
|
||||
transition: .4s;
|
||||
width: 26px;
|
||||
}
|
||||
|
||||
input:checked + .slider {
|
||||
background-color: #4D3DF7;
|
||||
}
|
||||
|
||||
input:checked + .slider:before {
|
||||
transform: translateX(26px);
|
||||
}
|
||||
|
||||
.slider.round {
|
||||
border-radius: 34px;
|
||||
}
|
||||
|
||||
.slider.round:before {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.CreatePoll_Switch {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.CreatePoll_Switch span {
|
||||
margin-right: 2rem;
|
||||
}
|
||||
|
||||
.Poll_Link {
|
||||
font-size: 1.2rem;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.Recap_Link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.Copy_Link {
|
||||
border: none;
|
||||
font-size: 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.text-green {
|
||||
color: #199473;
|
||||
}
|
||||
|
||||
.Recap_Links {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
<div class="Container">
|
||||
<img src="../../assets/flat_logo.png" alt="Logo Simba" height="50px" [ngStyle]="{ 'marginBottom': '1rem' }"/>
|
||||
|
||||
<div class="card">
|
||||
<p-toast></p-toast>
|
||||
<p-steps [model]="items" [readonly]="false" [(activeIndex)]="step"></p-steps>
|
||||
</div>
|
||||
<div [hidden]="step!=0" class="stepsdemo-content">
|
||||
<p-card>
|
||||
<ng-template pTemplate="title">
|
||||
Informations
|
||||
</ng-template>
|
||||
<ng-template pTemplate="subtitle">
|
||||
Entrez les informations sur le rendez-vous à planifier
|
||||
</ng-template>
|
||||
<ng-template pTemplate="content">
|
||||
<div class="p-fluid">
|
||||
<div class="p-field">
|
||||
<label for="titre">Titre de la réunion</label>
|
||||
<input #titre="ngModel" id="titre" type="text" required pInputText
|
||||
[(ngModel)]="poll.title"
|
||||
[ngClass]="{'p-invalid': (titre.invalid && submitted) || (titre.dirty && titre.invalid)}">
|
||||
<small *ngIf="(titre.invalid && submitted) || (titre.dirty && titre.invalid)"
|
||||
class="p-error">Le titre est requis.</small>
|
||||
</div>
|
||||
<div class="p-field">
|
||||
<label for="lieu">Lieu de la réunion</label>
|
||||
<input #lieu="ngModel" id="lieu" type="text" required pInputText
|
||||
[(ngModel)]="poll.location"
|
||||
[ngClass]="{'p-invalid': (lieu.invalid && submitted) || (lieu.dirty && lieu.invalid)}">
|
||||
<small class="p-error" *ngIf="(lieu.invalid && submitted )|| (lieu.dirty && lieu.invalid)">Le
|
||||
lieu est requis.</small>
|
||||
</div>
|
||||
<div class="p-field">
|
||||
<label for="desc">Description</label>
|
||||
<textarea #desc="ngModel" id="desc" required pInputTextarea
|
||||
[(ngModel)]="poll.description"
|
||||
[ngClass]="{'p-invalid': (desc.invalid && submitted) || (desc.dirty && desc.invalid)}"></textarea>
|
||||
<small class="p-error" *ngIf="(desc.invalid && submitted) || (desc.dirty && desc.invalid)">La description est requise.</small>
|
||||
</div>
|
||||
<div class="p-field">
|
||||
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 1rem;font-weight: normal;">Repas</p>
|
||||
<p-inputSwitch [ariaLabelledBy]="'repas'" #repas="ngModel" id="repas" [(ngModel)]="poll.has_meal"></p-inputSwitch>
|
||||
</div>
|
||||
<div *ngIf="poll?.id" class="p-field">
|
||||
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 1rem;font-weight: normal;">Sondage clos</p>
|
||||
<p-inputSwitch #clos="ngModel" id="clos" [(ngModel)]="poll.clos"></p-inputSwitch>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</ng-template>
|
||||
<ng-template pTemplate="footer">
|
||||
<div class="p-grid p-nogutter p-justify-end">
|
||||
<p-button [disabled]=true label="Back" icon="pi pi-angle-left"></p-button>
|
||||
|
||||
<p-button class="float-right" label="Next" (onClick)="nextPage()" icon="pi pi-angle-right" iconPos="right"></p-button>
|
||||
</div>
|
||||
</ng-template>
|
||||
</p-card>
|
||||
</div>
|
||||
<div *ngIf="step==1" class="stepsdemo-content">
|
||||
<p-card>
|
||||
<ng-template pTemplate="content">
|
||||
<div class="p-fluid">
|
||||
|
||||
<div class="p-field">
|
||||
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 1rem;font-weight: normal;">Avez vous un agenda avec un flux ics accessible ?</p>
|
||||
<p-inputSwitch [(ngModel)]="hasics"></p-inputSwitch>
|
||||
</div>
|
||||
<div *ngIf="hasics" class="p-field">
|
||||
<label for="ics">URL ICS du participant</label>
|
||||
<span class="p-input-icon-right">
|
||||
<i *ngIf="loadics" class="pi pi-spin pi-spinner" ></i>
|
||||
<input #mail="ngModel" id="ics" type="text" pInputText (change)="getICS()" [(ngModel)]="ics">
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<full-calendar #calendar [options]="options"></full-calendar>
|
||||
</ng-template>
|
||||
<ng-template pTemplate="footer">
|
||||
<div>
|
||||
<p-button label="Back" (onClick)="prevPage1()" icon="pi pi-angle-left"></p-button>
|
||||
<p-button class="float-right" label="Next" (onClick)="nextPage1()" icon="pi pi-angle-right" iconPos="right"></p-button>
|
||||
</div>
|
||||
|
||||
|
||||
</ng-template>
|
||||
</p-card>
|
||||
</div>
|
||||
<div *ngIf="step==2" class="stepsdemo-content">
|
||||
<p-card>
|
||||
<ng-template pTemplate="content">
|
||||
Le sondage est créé. <BR>
|
||||
Le lien pour participer est <a [href]="urlsondage" target="_blank">{{urlsondage}} </a>. <BR>
|
||||
Le lien d'administration est <a [href]="urlsondageadmin" target="_blank">{{urlsondageadmin}}</a>.<BR>
|
||||
Un salon a été créé de discussion pour cette réunion est accessible à cette adresse <a [href]="urlsalon" target="_blank">{{urlsalon}}</a>.<BR>
|
||||
<span *ngIf="urlpad">Un pad a été créé pour cette réunion <a [href]="urlpad" target="_blank">{{urlpad}}</a>.</span><BR>
|
||||
<BR>
|
||||
</ng-template>
|
||||
<ng-template pTemplate="footer">
|
||||
<div>
|
||||
<p-button label="Back" (onClick)="prevPage1()" icon="pi pi-angle-left"></p-button>
|
||||
</div>
|
||||
|
||||
|
||||
</ng-template>
|
||||
</p-card>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { CreatePollComponentComponent } from './create-poll-component.component';
|
||||
|
||||
describe('CreatePollComponentComponent', () => {
|
||||
let component: CreatePollComponentComponent;
|
||||
let fixture: ComponentFixture<CreatePollComponentComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ CreatePollComponentComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(CreatePollComponentComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,387 @@
|
||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||
import { MenuItem, MessageService } from 'primeng/api';
|
||||
import { PollService } from '../poll-service.service';
|
||||
import { FullCalendarComponent } from '@fullcalendar/angular';
|
||||
import frLocale from '@fullcalendar/core/locales/fr';
|
||||
import { PollChoice, Poll, User } from '../model/model';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { CalendarOptions, EventInput } from '@fullcalendar/core';
|
||||
import dayGridPlugin from '@fullcalendar/daygrid';
|
||||
import interactionPlugin from '@fullcalendar/interaction';
|
||||
import timeGridPlugin from '@fullcalendar/timegrid';
|
||||
|
||||
/*FullCalendarModule.registerPlugins([ // register FullCalendar plugins
|
||||
dayGridPlugin,
|
||||
interactionPlugin,
|
||||
timeGridPlugin
|
||||
]);*/
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app-create-poll-component',
|
||||
templateUrl: './create-poll-component.component.html',
|
||||
styleUrls: ['./create-poll-component.component.css'],
|
||||
providers: [MessageService, PollService, FullCalendarComponent]
|
||||
})
|
||||
export class CreatePollComponentComponent implements OnInit {
|
||||
urlsondage = '';
|
||||
urlsondageadmin = '';
|
||||
urlsalon = '';
|
||||
urlpad = '';
|
||||
|
||||
items: MenuItem[];
|
||||
options: CalendarOptions;
|
||||
|
||||
step = 0;
|
||||
|
||||
slugid: string;
|
||||
poll: Poll = {};
|
||||
|
||||
events: EventInput[] = [];
|
||||
eventsfromics: EventInput[] = [];
|
||||
allevents: EventInput[] = [];
|
||||
|
||||
|
||||
calendarComponent: FullCalendarComponent;
|
||||
hasics = false;
|
||||
loadics = false;
|
||||
ics: string;
|
||||
|
||||
@ViewChild('calendar') set content(content: FullCalendarComponent) {
|
||||
if (content) { // initially setter gets called with undefined
|
||||
this.calendarComponent = content;
|
||||
const calendarApi = this.calendarComponent.getApi();
|
||||
|
||||
this.poll.pollChoices.forEach(pc => {
|
||||
|
||||
const evt =
|
||||
{
|
||||
title: '',
|
||||
start: pc.startDate,
|
||||
end: pc.endDate,
|
||||
resourceEditable: false,
|
||||
eventResizableFromStart: false,
|
||||
extendedProps: {
|
||||
choiceid: pc.id,
|
||||
tmpId: this.getUniqueId(8)
|
||||
},
|
||||
};
|
||||
this.events.push(evt);
|
||||
calendarApi.addEvent(evt, true);
|
||||
|
||||
});
|
||||
calendarApi.setOption('validRange', {
|
||||
start: this.getValidDate(),
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
submitted = false;
|
||||
|
||||
|
||||
constructor(public messageService: MessageService, public pollService: PollService, private actRoute: ActivatedRoute) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.poll.pollChoices = [];
|
||||
this.items = [{
|
||||
label: 'Informations pour le rendez vous',
|
||||
command: () => {
|
||||
this.step = 0;
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Choix de la date',
|
||||
command: () => {
|
||||
this.step = 1;
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Résumé',
|
||||
command: () => {
|
||||
this.step = 2;
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
|
||||
this.options = {
|
||||
initialView: 'timeGridWeek',
|
||||
plugins: [dayGridPlugin, interactionPlugin, timeGridPlugin],
|
||||
|
||||
// dateClick: this.handleDateClick.bind(this), // bind is important!
|
||||
select: (selectionInfo) => {
|
||||
console.log(selectionInfo);
|
||||
const calendarApi = this.calendarComponent.getApi();
|
||||
console.log(this.getUniqueId(8));
|
||||
const evt = {
|
||||
title: '',
|
||||
start: selectionInfo.start,
|
||||
end: selectionInfo.end,
|
||||
resourceEditable: true,
|
||||
eventResizableFromStart: true,
|
||||
id: this.getUniqueId(8),
|
||||
|
||||
extendedProps: {
|
||||
// tmpId: this.getUniqueId(8)
|
||||
},
|
||||
};
|
||||
calendarApi.addEvent(evt, true);
|
||||
this.events.push(evt);
|
||||
this.allevents.push(evt);
|
||||
},
|
||||
|
||||
events: this.allevents,
|
||||
editable: true,
|
||||
droppable: true,
|
||||
// selectMirror: true,
|
||||
eventResizableFromStart: true,
|
||||
selectable: true,
|
||||
locale: frLocale,
|
||||
themeSystem: 'bootstrap',
|
||||
slotMinTime: '08:00:00',
|
||||
slotMaxTime: '20:00:00',
|
||||
eventMouseEnter: (mouseEnterInfo) => {
|
||||
|
||||
},
|
||||
eventDrop: (info) => {
|
||||
const evt = this.events.filter(e => e.id === info.event.id).pop();
|
||||
evt.start = info.event.start;
|
||||
evt.end = info.event.end;
|
||||
},
|
||||
eventResize: (info) => {
|
||||
const evt = this.events.filter(e => e.id === info.event.id).pop();
|
||||
const index = this.events.indexOf(evt);
|
||||
evt.start = info.event.start;
|
||||
evt.end = info.event.end;
|
||||
},
|
||||
eventClick: (info) => {
|
||||
const evt = this.events.filter(e => e.id === info.event.id).pop();
|
||||
if (evt != null){
|
||||
const index = this.events.indexOf(evt);
|
||||
if (index > -1) {
|
||||
this.events.splice(index, 1);
|
||||
}
|
||||
const index1 = this.allevents.indexOf(evt);
|
||||
if (index1 > -1) {
|
||||
this.allevents.splice(index1, 1);
|
||||
}
|
||||
info.event.remove();
|
||||
}
|
||||
|
||||
},
|
||||
validRange: {
|
||||
start: Date.now()
|
||||
}
|
||||
};
|
||||
|
||||
this.actRoute.paramMap.subscribe(params => {
|
||||
this.slugid = params.get('slugadminid');
|
||||
console.log(this.slugid);
|
||||
|
||||
if (this.slugid != null) {
|
||||
|
||||
this.pollService.getPollBySlugAdminId(this.slugid).subscribe(p => {
|
||||
if (p != null) {
|
||||
this.poll = p;
|
||||
} else {
|
||||
this.messageService.add(
|
||||
{
|
||||
severity: 'warn',
|
||||
summary: 'Un sondage avec cet identifiant n\'existe pas',
|
||||
detail: 'Le sondage n\'a pas été récupéré'
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
nextPage(): void {
|
||||
|
||||
if (this.poll.title && this.poll.location && this.poll.description) {
|
||||
this.step = 1;
|
||||
|
||||
|
||||
return;
|
||||
}
|
||||
this.messageService.add(
|
||||
{
|
||||
severity: 'warn',
|
||||
summary: 'Données incomplètes',
|
||||
detail: 'Veuillez remplir les champs requis'
|
||||
}
|
||||
);
|
||||
|
||||
this.submitted = true;
|
||||
}
|
||||
|
||||
nextPage1(): void {
|
||||
console.log(this.poll.id);
|
||||
if (this.poll.id == null) {
|
||||
this.events.forEach(e => {
|
||||
this.poll.pollChoices.push({
|
||||
startDate: e.start as any,
|
||||
endDate: e.end as any,
|
||||
});
|
||||
});
|
||||
this.pollService.createPoll(this.poll).subscribe(p1 => {
|
||||
this.poll = p1;
|
||||
this.urlsondage = window.location.protocol + '//' + window.location.host + '/answer/' + p1.slug;
|
||||
this.urlsondageadmin = window.location.protocol + '//' + window.location.host + '/admin/' + p1.slugAdmin;
|
||||
this.urlsalon = p1.tlkURL;
|
||||
this.urlpad = p1.padURL;
|
||||
this.step = 2;
|
||||
});
|
||||
} else {
|
||||
|
||||
const toKeep: PollChoice[] = [];
|
||||
this.events.filter(c => c.extendedProps != null && c.extendedProps.choiceid != null).forEach(e => {
|
||||
toKeep.push(this.poll.pollChoices.filter(c1 => c1.id === e.extendedProps.choiceid)[0]);
|
||||
});
|
||||
this.poll.pollChoices = toKeep;
|
||||
this.poll.pollChoices.forEach(c => {
|
||||
const res = this.events.filter(c1 => c1.extendedProps != null &&
|
||||
c1.extendedProps.choiceid != null && c1.extendedProps.choiceid === c.id)[0];
|
||||
c.startDate = res.start as any;
|
||||
c.endDate = res.end as any;
|
||||
});
|
||||
|
||||
this.events.filter(c => c.extendedProps == null || c.extendedProps.choiceid == null).forEach(e => {
|
||||
this.poll.pollChoices.push({
|
||||
startDate: e.start as any,
|
||||
endDate: e.end as any,
|
||||
});
|
||||
});
|
||||
console.log(this.events);
|
||||
console.log(this.poll.pollChoices);
|
||||
|
||||
this.pollService.updtatePoll(this.poll).subscribe(p1 => {
|
||||
this.poll = p1;
|
||||
this.urlsondage = 'http://localhost:4200/answer/' + p1.slug;
|
||||
this.urlsondageadmin = 'http://localhost:4200/admin/' + p1.slugAdmin;
|
||||
this.urlsalon = p1.tlkURL;
|
||||
this.urlpad = p1.padURL;
|
||||
this.step = 2;
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
prevPage1(): void {
|
||||
|
||||
this.step = this.step - 1;
|
||||
}
|
||||
|
||||
|
||||
private getUniqueId(parts: number): string {
|
||||
const stringArr = [];
|
||||
for (let i = 0; i < parts; i++) {
|
||||
// tslint:disable-next-line:no-bitwise
|
||||
const S4 = (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
|
||||
stringArr.push(S4);
|
||||
}
|
||||
return stringArr.join('-');
|
||||
}
|
||||
|
||||
private getValidDate(): number {
|
||||
if (this.poll.id != null) {
|
||||
if ((this.poll.pollChoices[0].startDate as any - Date.now()) < 0) {
|
||||
return this.poll.pollChoices[0].startDate as any;
|
||||
}
|
||||
}
|
||||
return Date.now();
|
||||
|
||||
}
|
||||
|
||||
|
||||
getICS(): void {
|
||||
this.loadics = true;
|
||||
this.pollService.getICS(this.slugid, this.ics).subscribe(res => {
|
||||
this.loadics = false;
|
||||
|
||||
const calendarApi = this.calendarComponent.getApi();
|
||||
if (res.eventdtos.length > 0) {
|
||||
this.eventsfromics.forEach(eid => {
|
||||
const index = this.allevents.indexOf(eid);
|
||||
if (index > -1) {
|
||||
this.allevents.splice(index, 1);
|
||||
}
|
||||
calendarApi.getEventById(eid.id)?.remove();
|
||||
});
|
||||
this.eventsfromics = [];
|
||||
}
|
||||
console.log(res);
|
||||
|
||||
res.eventdtos.forEach(evtdto => { // calendarApi.next();
|
||||
const evt1 =
|
||||
{
|
||||
title: evtdto.description,
|
||||
start: evtdto.startDate,
|
||||
end: evtdto.endDate,
|
||||
resourceEditable: false,
|
||||
editable: false,
|
||||
droppable: false,
|
||||
selectable: false,
|
||||
eventResizableFromStart: false,
|
||||
id: this.getUniqueId(8),
|
||||
|
||||
backgroundColor: 'red',
|
||||
extendedProps: {
|
||||
fromics: true
|
||||
},
|
||||
|
||||
|
||||
};
|
||||
const eventAPI = calendarApi.addEvent(evt1, true);
|
||||
this.eventsfromics.push(evt1);
|
||||
this.allevents.push(evt1);
|
||||
|
||||
});
|
||||
|
||||
const unselected = this.events.map(ev => ev.extendedProps.choiceid);
|
||||
res.selectedChoices.forEach(e => {
|
||||
const index = unselected.indexOf(e);
|
||||
if (index > -1) {
|
||||
unselected.splice(index, 1);
|
||||
}
|
||||
const evt1 = this.events.filter(ev => ev.extendedProps.choiceid === e)[0];
|
||||
|
||||
const evt2 = calendarApi.getEventById(evt1.id);
|
||||
evt1.backgroundColor = 'red';
|
||||
evt1.extendedProps.selected = false;
|
||||
evt2.setProp('backgroundColor', 'red');
|
||||
// this.poll.pollChoices.filter(pc => pc.id === evt1.extendedProps.choiceid)[0].users.push({ id: -1 });
|
||||
});
|
||||
unselected.forEach(e => {
|
||||
const evt1 = this.events.filter(ev => ev.extendedProps.choiceid === e)[0];
|
||||
|
||||
const evt2 = calendarApi.getEventById(evt1.id);
|
||||
evt1.backgroundColor = 'green';
|
||||
evt1.extendedProps.selected = true;
|
||||
evt2.setProp('backgroundColor', 'green');
|
||||
this.poll.pollChoices.filter(pc => pc.id === evt1.extendedProps.choiceid)[0].users.push({ id: -1 });
|
||||
});
|
||||
}, (err) => {
|
||||
this.loadics = false;
|
||||
|
||||
this.messageService.add(
|
||||
{
|
||||
severity: 'warn',
|
||||
summary: 'Ne peut récupérer l\'agenda à partir de l\'adresse de l\'ics',
|
||||
detail: 'Une erreur s\'est produite au moment de la récupération de l\'agenda'
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { DateagoPipe } from './dateago.pipe';
|
||||
|
||||
describe('DateagoPipe', () => {
|
||||
it('create an instance', () => {
|
||||
const pipe = new DateagoPipe();
|
||||
expect(pipe).toBeTruthy();
|
||||
});
|
||||
});
|
||||
41
ansible/files/doodlestudent/front/src/app/dateago.pipe.ts
Normal file
41
ansible/files/doodlestudent/front/src/app/dateago.pipe.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
|
||||
@Pipe({
|
||||
name: 'dateago',
|
||||
pure: true
|
||||
|
||||
})
|
||||
export class DateagoPipe implements PipeTransform {
|
||||
|
||||
transform(value: any, args?: any): any {
|
||||
if (value) {
|
||||
const seconds = Math.floor((+new Date() - +new Date(value)) / 1000);
|
||||
if (seconds < 29) { // less than 30 seconds ago will show as 'Just now'
|
||||
return 'quelques secondes';
|
||||
}
|
||||
const intervals = {
|
||||
année: 31536000,
|
||||
mois: 2592000,
|
||||
semaine: 604800,
|
||||
jour: 86400,
|
||||
heure: 3600,
|
||||
minute: 60,
|
||||
seconde: 1
|
||||
};
|
||||
let counter;
|
||||
// tslint:disable-next-line:forin
|
||||
for (const i in intervals) {
|
||||
counter = Math.floor(seconds / intervals[i]);
|
||||
if (counter > 0){
|
||||
if (counter === 1 || i === 'mois') {
|
||||
return counter + ' ' + i + ''; // singular (1 day ago)
|
||||
} else {
|
||||
return counter + ' ' + i + 's'; // plural (2 days ago)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
|
||||
|
||||
export class Card {
|
||||
constructor(public image: string, public style: any, public title: string, public subtitle: string){
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
.Container {
|
||||
max-width: 800px;
|
||||
margin: 2rem auto;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.Home_Container {
|
||||
width: 1024px;
|
||||
margin: 0 auto;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.Home_Wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.Home_Logo {
|
||||
margin-bottom: 2rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.Home_Button {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.Home_CreateLink {
|
||||
padding: 1rem;
|
||||
width: 100%;
|
||||
background-color: #43dbac;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
font-weight: 700;
|
||||
font-size: 1.3rem;
|
||||
box-shadow: 0 2px 4px 0 rgba(0,0,0,0.10);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<div class="Home_Container">
|
||||
<div class="Home_Wrapper">
|
||||
<div class="Home_Logo">
|
||||
<img src="../../assets/Logo.png" alt="Logo Simba" height="130px"/>
|
||||
</div>
|
||||
<app-card-small-component [cards]="cards"></app-card-small-component>
|
||||
<div class="Home_Button">
|
||||
<a routerLink="/create" class="Home_CreateLink">Créer votre poll !</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { HomeComponentComponent } from './home-component.component';
|
||||
|
||||
describe('HomeComponentComponent', () => {
|
||||
let component: HomeComponentComponent;
|
||||
let fixture: ComponentFixture<HomeComponentComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ HomeComponentComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(HomeComponentComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { CardSmallComponentComponent } from '../card-small-component/card-small-component.component';
|
||||
import {Card} from './Card';
|
||||
@Component({
|
||||
selector: 'app-home-component',
|
||||
templateUrl: './home-component.component.html',
|
||||
styleUrls: ['./home-component.component.css'],
|
||||
providers: [CardSmallComponentComponent]
|
||||
|
||||
})
|
||||
export class HomeComponentComponent implements OnInit {
|
||||
|
||||
constructor() { }
|
||||
|
||||
|
||||
cards: Card[] = [];
|
||||
|
||||
ngOnInit(): void {
|
||||
|
||||
this.cards.push(new Card('assets/1.png', {backgroundColor: '#44baf2', color: 'white'}, 'Créez un sondage', 'Définissez plusieurs créneaux pour votre réunion.'));
|
||||
this.cards.push(new Card('assets/2.png', {backgroundColor: '#fc506d', color: 'white'}, 'Envoyez vos invitations', 'Les participants aux sondages pourront voter pour les dates qui leur conviennent le mieux !'));
|
||||
this.cards.push(new Card('assets/3.png', {backgroundColor: '#8f3ee8', color: 'white'}, 'Faites votre choix', 'Vous pourrez obtenir en direct les résultats du sondage afin de choisir au mieux la meilleure proposition.'));
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<div class="modal-content">
|
||||
<div class="modal-header text-center justify-content-center">
|
||||
<h4 class="modal-title" id="modal-basic-title">Le sondage est maintenant clos</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
||||
<div class="d-flex justify-content-center">
|
||||
La date retenue pour le soundage est le:
|
||||
</div>
|
||||
<BR>
|
||||
<div class="d-flex justify-content-center">
|
||||
<b>{{poll?.selectedChoice?.startDate | date:'EEEE d LLLL': 'CEST':'fr'}}</b>
|
||||
</div>
|
||||
<div class="d-flex justify-content-center">
|
||||
<b> de {{poll?.selectedChoice?.startDate | date:'H:mm'}}</b>
|
||||
</div>
|
||||
<div class="d-flex justify-content-center">
|
||||
<b> - </b>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-center">
|
||||
<b> {{poll?.selectedChoice?.endDate | date:'H:mm'}}</b>
|
||||
</div>
|
||||
<BR>
|
||||
Le lieu sera {{poll?.location}}
|
||||
<BR>
|
||||
<div *ngIf="poll?.padURL">
|
||||
Un pad est ouvert <a [href]="poll?.padURL" target="_blank">ici</a>
|
||||
</div>
|
||||
<BR>
|
||||
<div *ngIf="poll?.tlkURL">
|
||||
Un salon de discussion est ouvert <a [href]="poll?.tlkURL" target="_blank">ici</a>
|
||||
</div>
|
||||
<BR>
|
||||
<div *ngIf="poll?.has_meal">Un repas est prévu pour ce meeting.
|
||||
</div>
|
||||
<BR>
|
||||
<div *ngIf="poll?.description">
|
||||
L'ordre du jour est le suivant:
|
||||
{{poll.description}}
|
||||
</div>
|
||||
<BR>
|
||||
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline-dark" (click)="dismissModalAndNavigate()">Retour</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ModalPollClosComponent } from './modal-poll-clos.component';
|
||||
|
||||
describe('ModalPollClosComponent', () => {
|
||||
let component: ModalPollClosComponent;
|
||||
let fixture: ComponentFixture<ModalPollClosComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ ModalPollClosComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ModalPollClosComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
import { Component, OnInit, Input } from '@angular/core';
|
||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { Poll } from '../model/model';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'app-modal-poll-clos',
|
||||
templateUrl: './modal-poll-clos.component.html',
|
||||
styleUrls: ['./modal-poll-clos.component.css'],
|
||||
providers: [NgbActiveModal]
|
||||
})
|
||||
export class ModalPollClosComponent implements OnInit {
|
||||
|
||||
@Input() poll: Poll;
|
||||
constructor(public activeModal: NgbActiveModal, public router: Router) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
dismissModalAndNavigate(): void{
|
||||
this.activeModal.close();
|
||||
window.location.href = '/';
|
||||
|
||||
}
|
||||
}
|
||||
57
ansible/files/doodlestudent/front/src/app/model/model.ts
Normal file
57
ansible/files/doodlestudent/front/src/app/model/model.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
export interface Poll {
|
||||
createdAt?: Date;
|
||||
description?: string;
|
||||
has_meal?: boolean;
|
||||
id?: number;
|
||||
location?: string;
|
||||
padURL?: string;
|
||||
pollChoices?: PollChoice[];
|
||||
selectedChoice ?: PollChoice;
|
||||
pollComments?: PollCommentElement[];
|
||||
pollMealPreferences?: PollCommentElement[];
|
||||
slug?: string;
|
||||
slugAdmin?: string;
|
||||
title?: string;
|
||||
tlkURL?: string;
|
||||
updatedAt?: Date;
|
||||
clos ?: boolean;
|
||||
}
|
||||
|
||||
export interface PollChoice {
|
||||
endDate?: Date;
|
||||
id?: number;
|
||||
startDate?: Date;
|
||||
users?: User[];
|
||||
}
|
||||
|
||||
export interface User {
|
||||
id?: number;
|
||||
username?: string;
|
||||
mail?: string;
|
||||
}
|
||||
|
||||
export interface ChoiceUser {
|
||||
username?: string;
|
||||
mail?: string;
|
||||
pref?: string;
|
||||
ics?: string;
|
||||
choices?: number[];
|
||||
}
|
||||
|
||||
export interface PollCommentElement {
|
||||
content?: string;
|
||||
id?: number;
|
||||
auteur?: string;
|
||||
}
|
||||
|
||||
export interface EventDTO{
|
||||
startDate?: Date;
|
||||
endDate?: Date;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
|
||||
export interface EventDTOAndSelectedChoice {
|
||||
eventdtos?: EventDTO[];
|
||||
selectedChoices?: number[];
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { PollServiceService } from './poll-service.service';
|
||||
|
||||
describe('PollServiceService', () => {
|
||||
let service: PollServiceService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(PollServiceService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,57 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Poll, PollChoice, User, ChoiceUser, PollCommentElement, EventDTOAndSelectedChoice } from './model/model';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class PollService {
|
||||
|
||||
constructor(private http: HttpClient) { }
|
||||
|
||||
public createPoll(p: Poll): Observable<Poll> {
|
||||
console.log('create poll');
|
||||
return this.http.post<Poll>('/api/polls', p);
|
||||
}
|
||||
|
||||
|
||||
public updtatePoll(p: Poll): Observable<Poll> {
|
||||
return this.http.put<Poll>('/api/poll/update1', p);
|
||||
}
|
||||
|
||||
|
||||
public getPollBySlugId(slugId: string): Observable<Poll>{
|
||||
return this.http.get<Poll>('/api/poll/slug/' + slugId);
|
||||
}
|
||||
|
||||
public getComentsBySlugId(slugId: string): Observable<PollCommentElement[]>{
|
||||
return this.http.get<PollCommentElement[]>('/api/polls/' + slugId + '/comments');
|
||||
}
|
||||
|
||||
public getPollBySlugAdminId(slugId: string): Observable<Poll>{
|
||||
return this.http.get<Poll>('/api/poll/aslug/' + slugId);
|
||||
|
||||
}
|
||||
|
||||
public updateChoice4user( cu: ChoiceUser): Observable<User>{
|
||||
|
||||
return this.http.post<User>('/api/poll/choiceuser/', cu);
|
||||
}
|
||||
|
||||
public addComment4Poll( slug: string, comment: PollCommentElement ): Observable<PollCommentElement>{
|
||||
|
||||
return this.http.post<PollCommentElement>('/api/poll/comment/' + slug, comment);
|
||||
}
|
||||
|
||||
selectEvent(choiceid: number): Observable<void> {
|
||||
return this.http.post<void>('/api/poll/selectedchoice/' + choiceid, null);
|
||||
|
||||
}
|
||||
|
||||
getICS(slug: string, ics: string): Observable<EventDTOAndSelectedChoice> {
|
||||
return this.http.get<EventDTOAndSelectedChoice>('/api/ics/polls/' + slug + '/' + btoa(ics));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { Selecteddate4userPipePipe } from './selecteddate4user-pipe.pipe';
|
||||
|
||||
describe('Selecteddate4userPipePipe', () => {
|
||||
it('create an instance', () => {
|
||||
const pipe = new Selecteddate4userPipePipe();
|
||||
expect(pipe).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import { PollChoice } from './model/model';
|
||||
import { EventInput } from '@fullcalendar/core';
|
||||
|
||||
@Pipe({
|
||||
name: 'selecteddate4userPipe'
|
||||
})
|
||||
export class Selecteddate4userPipePipe implements PipeTransform {
|
||||
|
||||
transform(value: PollChoice[], id: number, ev: EventInput): boolean {
|
||||
return value.map( e => e.id).includes(ev.extendedProps.choiceid);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
.Author_MealPref {
|
||||
font-weight: bold;
|
||||
color: #4d3cf7;
|
||||
}
|
||||
|
||||
.Author_Comment {
|
||||
font-weight: bold;
|
||||
color: #4d3cf7;
|
||||
}
|
||||
.MealPref {
|
||||
padding: 1rem 0;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
.Comment {
|
||||
padding: 1rem;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<div class="Comments">
|
||||
<div class="Comment">
|
||||
<h2>{{comments?.length}} commentaire<span *ngIf="comments?.length > 1">s</span> : </h2>
|
||||
<ul>
|
||||
<li *ngFor="let comment of comments" class="MealPref">
|
||||
<span class="Author_Comment">{{ comment.auteur}}</span> : <span>{{comment.content}}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ShowCommentsComponent } from './show-comments.component';
|
||||
|
||||
describe('ShowCommentsComponent', () => {
|
||||
let component: ShowCommentsComponent;
|
||||
let fixture: ComponentFixture<ShowCommentsComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ ShowCommentsComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ShowCommentsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,22 @@
|
||||
import { Component, OnInit, Input } from '@angular/core';
|
||||
import { PollCommentElement } from '../model/model';
|
||||
import { PollService } from '../poll-service.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-show-comments',
|
||||
templateUrl: './show-comments.component.html',
|
||||
styleUrls: ['./show-comments.component.css']
|
||||
})
|
||||
export class ShowCommentsComponent implements OnInit {
|
||||
|
||||
|
||||
@Input()
|
||||
comments: PollCommentElement[];
|
||||
|
||||
constructor() { }
|
||||
|
||||
ngOnInit(): void {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
.Links {
|
||||
display: flex;
|
||||
font-weight: 600;
|
||||
width: 100%;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
background-color: #4D3DF7;
|
||||
border-radius: 5px 5px 0 0;
|
||||
box-shadow: 0 2px 4px 0 rgba(0,0,0,0.10);
|
||||
}
|
||||
|
||||
.Links_Right {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.Links_Left {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.Feat_Link {
|
||||
padding: 0.7rem 1rem;
|
||||
text-decoration: none;
|
||||
color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
transition: all 0.3s linear;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.Feat_Link:last-child {
|
||||
border-radius: 0 5px 0 0;
|
||||
}
|
||||
|
||||
.Feat_Link.Unique {
|
||||
border-radius: 5px 5px 0 0!important;
|
||||
}
|
||||
|
||||
.Feat_Link:hover {
|
||||
background-color: #0C008C;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<div class="Links">
|
||||
<div class="Links_Left">
|
||||
<a routerLink="/create" class="Feat_Link Unique">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" style="margin-right: 0.5rem" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-plus"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>
|
||||
Nouveau
|
||||
</a>
|
||||
<a *ngIf="adminSlug && adminSlug!=''" [href]="'/update/' + adminSlug" class="Feat_Link Unique">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" style="margin-right: 0.5rem" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="feather feather-edit"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path></svg>
|
||||
Modifier
|
||||
</a>
|
||||
|
||||
<!--onClick={() => copy(`${window.location.protocol}//${window.location.host}/polls/${token}`)}-->
|
||||
<a (click)="copyMessage()" class="Feat_Link">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" style="margin-right: 0.5rem" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-share"><path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8"></path><polyline points="16 6 12 2 8 6"></polyline><line x1="12" y1="2" x2="12" y2="15"></line></svg>
|
||||
Partager
|
||||
</a>
|
||||
</div>
|
||||
<div class="Links_Right">
|
||||
<a *ngIf="talkToURL && talkToURL!=''" [href]="talkToURL" class="Feat_Link" target="_blank" rel="noopener noreferrer">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" style="margin-right: 0.5rem" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" class="feather feather-message-circle"><path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"></path></svg>
|
||||
Chat
|
||||
</a>
|
||||
<a *ngIf="padURL && padURL!=''" [href]="padURL" class="Feat_Link" target="_blank" rel="noopener noreferrer">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" style="margin-right: 0.5rem" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" class="feather feather-paperclip"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"></path></svg>
|
||||
Pad
|
||||
</a>
|
||||
<a *ngIf="adminSlug && adminSlug!=''" class="Feat_Link" [href]="getExcelUrl()" target="_blank" rel="noopener noreferrer">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" style="margin-right: 0.5rem" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" class="feather feather-download"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>
|
||||
Exporter
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { TopBarComponent } from './top-bar.component';
|
||||
|
||||
describe('TopBarComponent', () => {
|
||||
let component: TopBarComponent;
|
||||
let fixture: ComponentFixture<TopBarComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ TopBarComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(TopBarComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,47 @@
|
||||
import { Component, OnInit, Input } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-top-bar',
|
||||
templateUrl: './top-bar.component.html',
|
||||
styleUrls: ['./top-bar.component.css']
|
||||
})
|
||||
export class TopBarComponent implements OnInit {
|
||||
|
||||
|
||||
@Input()
|
||||
padURL: string ;
|
||||
|
||||
@Input()
|
||||
talkToURL: string ;
|
||||
|
||||
@Input()
|
||||
adminSlug: string;
|
||||
|
||||
@Input()
|
||||
slug: string;
|
||||
|
||||
|
||||
constructor() { }
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
copyMessage(): void {
|
||||
const selBox = document.createElement('textarea');
|
||||
selBox.style.position = 'fixed';
|
||||
selBox.style.left = '0';
|
||||
selBox.style.top = '0';
|
||||
selBox.style.opacity = '0';
|
||||
selBox.value = window.location.protocol + '//' + window.location.host + '/answer/' + this.slug;
|
||||
document.body.appendChild(selBox);
|
||||
selBox.focus();
|
||||
selBox.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(selBox);
|
||||
}
|
||||
|
||||
getExcelUrl(): string {
|
||||
return window.location.protocol + '//' + window.location.host + '/api/polls/' + this.slug + '/results';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { UsernamePipePipe } from './username-pipe.pipe';
|
||||
|
||||
describe('UsernamePipePipe', () => {
|
||||
it('create an instance', () => {
|
||||
const pipe = new UsernamePipePipe();
|
||||
expect(pipe).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import { User } from './model/model';
|
||||
|
||||
@Pipe({
|
||||
name: 'usernamePipe'
|
||||
})
|
||||
export class UsernamePipePipe implements PipeTransform {
|
||||
|
||||
transform(value: User[], id: number): string {
|
||||
return value.filter(u => u.id === id)[0].username;
|
||||
}
|
||||
|
||||
}
|
||||
BIN
ansible/files/doodlestudent/front/src/assets/1.png
Normal file
BIN
ansible/files/doodlestudent/front/src/assets/1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 71 KiB |
BIN
ansible/files/doodlestudent/front/src/assets/2.png
Normal file
BIN
ansible/files/doodlestudent/front/src/assets/2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 292 KiB |
BIN
ansible/files/doodlestudent/front/src/assets/3.png
Normal file
BIN
ansible/files/doodlestudent/front/src/assets/3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 304 KiB |
BIN
ansible/files/doodlestudent/front/src/assets/Logo.png
Normal file
BIN
ansible/files/doodlestudent/front/src/assets/Logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
BIN
ansible/files/doodlestudent/front/src/assets/flat_logo.png
Normal file
BIN
ansible/files/doodlestudent/front/src/assets/flat_logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
300020
ansible/files/doodlestudent/front/src/assets/test.ics
Normal file
300020
ansible/files/doodlestudent/front/src/assets/test.ics
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,3 @@
|
||||
export const environment = {
|
||||
production: true
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
// This file can be replaced during build by using the `fileReplacements` array.
|
||||
// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
|
||||
// The list of file replacements can be found in `angular.json`.
|
||||
|
||||
export const environment = {
|
||||
production: false
|
||||
};
|
||||
|
||||
/*
|
||||
* For easier debugging in development mode, you can import the following file
|
||||
* to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
|
||||
*
|
||||
* This import should be commented out in production mode because it will have a negative impact
|
||||
* on performance if an error is thrown.
|
||||
*/
|
||||
// import 'zone.js/dist/zone-error'; // Included with Angular CLI.
|
||||
BIN
ansible/files/doodlestudent/front/src/favicon.ico
Normal file
BIN
ansible/files/doodlestudent/front/src/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 948 B |
13
ansible/files/doodlestudent/front/src/index.html
Normal file
13
ansible/files/doodlestudent/front/src/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Tlcfront</title>
|
||||
<base href="/">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
</head>
|
||||
<body>
|
||||
<app-root></app-root>
|
||||
</body>
|
||||
</html>
|
||||
12
ansible/files/doodlestudent/front/src/main.ts
Normal file
12
ansible/files/doodlestudent/front/src/main.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { enableProdMode } from '@angular/core';
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
|
||||
import { AppModule } from './app/app.module';
|
||||
import { environment } from './environments/environment';
|
||||
|
||||
if (environment.production) {
|
||||
enableProdMode();
|
||||
}
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(AppModule)
|
||||
.catch(err => console.error(err));
|
||||
67
ansible/files/doodlestudent/front/src/polyfills.ts
Normal file
67
ansible/files/doodlestudent/front/src/polyfills.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
/***************************************************************************************************
|
||||
* Load `$localize` onto the global scope - used if i18n tags appear in Angular templates.
|
||||
*/
|
||||
import '@angular/localize/init';
|
||||
/**
|
||||
* This file includes polyfills needed by Angular and is loaded before the app.
|
||||
* You can add your own extra polyfills to this file.
|
||||
*
|
||||
* This file is divided into 2 sections:
|
||||
* 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
|
||||
* 2. Application imports. Files imported after ZoneJS that should be loaded before your main
|
||||
* file.
|
||||
*
|
||||
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that
|
||||
* automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
|
||||
* Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
|
||||
*
|
||||
* Learn more in https://angular.io/guide/browser-support
|
||||
*/
|
||||
|
||||
/***************************************************************************************************
|
||||
* BROWSER POLYFILLS
|
||||
*/
|
||||
|
||||
/** IE10 and IE11 requires the following for NgClass support on SVG elements */
|
||||
// import 'classlist.js'; // Run `npm install --save classlist.js`.
|
||||
|
||||
/**
|
||||
* Web Animations `@angular/platform-browser/animations`
|
||||
* Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
|
||||
* Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
|
||||
*/
|
||||
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
|
||||
|
||||
/**
|
||||
* By default, zone.js will patch all possible macroTask and DomEvents
|
||||
* user can disable parts of macroTask/DomEvents patch by setting following flags
|
||||
* because those flags need to be set before `zone.js` being loaded, and webpack
|
||||
* will put import in the top of bundle, so user need to create a separate file
|
||||
* in this directory (for example: zone-flags.ts), and put the following flags
|
||||
* into that file, and then add the following code before importing zone.js.
|
||||
* import './zone-flags';
|
||||
*
|
||||
* The flags allowed in zone-flags.ts are listed here.
|
||||
*
|
||||
* The following flags will work for all browsers.
|
||||
*
|
||||
* (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
|
||||
* (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
|
||||
* (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
|
||||
*
|
||||
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
|
||||
* with the following flag, it will bypass `zone.js` patch for IE/Edge
|
||||
*
|
||||
* (window as any).__Zone_enable_cross_context_check = true;
|
||||
*
|
||||
*/
|
||||
|
||||
/***************************************************************************************************
|
||||
* Zone JS is required by default for Angular itself.
|
||||
*/
|
||||
import 'zone.js/dist/zone'; // Included with Angular CLI.
|
||||
|
||||
|
||||
/***************************************************************************************************
|
||||
* APPLICATION IMPORTS
|
||||
*/
|
||||
32
ansible/files/doodlestudent/front/src/styles.css
Normal file
32
ansible/files/doodlestudent/front/src/styles.css
Normal file
@@ -0,0 +1,32 @@
|
||||
/* You can add global styles to this file, and also import other style files */
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #F0F4F8;
|
||||
color: #303030;
|
||||
font-family: 'Montserrat', 'Open Sans', 'Roboto', sans-serif;
|
||||
}
|
||||
|
||||
.Btn-primary {
|
||||
background-color: #4D3DF7;
|
||||
border-radius: .25rem;
|
||||
font-weight: 600;
|
||||
padding: 0.5rem 1.5rem;
|
||||
font-size: 1.2rem;
|
||||
color: #fff;
|
||||
border: 0 solid #dae1e7;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 2px 4px 0 rgba(0,0,0,0.10);
|
||||
}
|
||||
|
||||
.Btn-primary:hover {
|
||||
background-color: #3525E6;
|
||||
}
|
||||
|
||||
25
ansible/files/doodlestudent/front/src/test.ts
Normal file
25
ansible/files/doodlestudent/front/src/test.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
|
||||
|
||||
import 'zone.js/dist/zone-testing';
|
||||
import { getTestBed } from '@angular/core/testing';
|
||||
import {
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting
|
||||
} from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
declare const require: {
|
||||
context(path: string, deep?: boolean, filter?: RegExp): {
|
||||
keys(): string[];
|
||||
<T>(id: string): T;
|
||||
};
|
||||
};
|
||||
|
||||
// First, initialize the Angular testing environment.
|
||||
getTestBed().initTestEnvironment(
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting()
|
||||
);
|
||||
// Then we find all the tests.
|
||||
const context = require.context('./', true, /\.spec\.ts$/);
|
||||
// And load the modules.
|
||||
context.keys().map(context);
|
||||
Reference in New Issue
Block a user