22 Commits

Author SHA1 Message Date
76c3b76153 Merge pull request 'Messaging system' (#150) from tonitch/Clyde:msg into master
All checks were successful
Build and test backend / Build-backend (push) Successful in 3m45s
deploy to production / deploy-frontend (push) Successful in 26s
deploy to production / deploy-backend (push) Successful in 1m35s
Build and test FrontEnd / Build-frontend (push) Successful in 24s
Reviewed-on: #150
2024-04-09 17:08:57 +02:00
5bb7606721 signing and commenting
All checks were successful
Build and test backend / Build-backend (pull_request) Successful in 2m21s
Build and test FrontEnd / Build-frontend (pull_request) Successful in 25s
2024-04-05 12:54:51 +02:00
b049c46571 Avoid title reseting mid modification
All checks were successful
Build and test backend / Build-backend (pull_request) Successful in 3m1s
Build and test FrontEnd / Build-frontend (pull_request) Successful in 27s
2024-04-05 11:45:47 +02:00
7ca5c34afe adding timestamp
All checks were successful
Build and test backend / Build-backend (pull_request) Successful in 2m9s
Build and test FrontEnd / Build-frontend (pull_request) Successful in 25s
2024-04-02 11:08:58 +02:00
2b9bdf8dac fixup! show sender on message
All checks were successful
Build and test backend / Build-backend (pull_request) Successful in 2m8s
Build and test FrontEnd / Build-frontend (pull_request) Successful in 24s
2024-04-02 10:18:50 +02:00
ccb954e348 show sender on message
All checks were successful
Build and test backend / Build-backend (pull_request) Successful in 2m9s
Build and test FrontEnd / Build-frontend (pull_request) Successful in 24s
2024-04-02 10:05:17 +02:00
ce56e37a33 redesign send bar
All checks were successful
Build and test backend / Build-backend (pull_request) Successful in 2m54s
Build and test FrontEnd / Build-frontend (pull_request) Successful in 27s
2024-03-29 14:31:22 +01:00
1c61a356a4 update when message sent 2024-03-29 14:31:08 +01:00
2bdffe6ab4 remove member of a discussion 2024-03-29 14:30:46 +01:00
729d1ad504 adding members management
All checks were successful
Build and test backend / Build-backend (pull_request) Successful in 3m35s
Build and test FrontEnd / Build-frontend (pull_request) Successful in 26s
2024-03-27 23:54:59 +01:00
1522d74ed3 messsaging on the frontend 2024-03-27 19:52:48 +01:00
b4499e04c7 Managing the message with backend
Using DTO to change the way discussions and message are sent to the
client
2024-03-27 19:50:52 +01:00
914f6bdf36 fix for new discussions not appearing right away 2024-03-25 09:43:45 +01:00
66e7fa24a1 Adding the discussion architectures and creating new discussions 2024-03-25 00:08:44 +01:00
7b9f021c24 first version of the frontend for messages 2024-03-24 22:33:17 +01:00
2dfa0a0ee0 base msg 2024-03-24 22:33:14 +01:00
9e0db361b8 Merge pull request 'Make app use full space' (#148) from tonitch/front/fullSpaceApp into master
All checks were successful
Build and test backend / Build-backend (push) Successful in 2m26s
deploy to production / deploy-frontend (push) Successful in 27s
deploy to production / deploy-backend (push) Successful in 2m5s
Build and test FrontEnd / Build-frontend (push) Successful in 24s
Reviewed-on: #148
Reviewed-by: Wal <karpinskiwal@gmail.com>
Reviewed-by: Maxime <231026@umons.ac.be>
2024-03-24 22:32:46 +01:00
7a13d412f1 Full screen apps
All checks were successful
Build and test backend / Build-backend (pull_request) Successful in 1m48s
Build and test FrontEnd / Build-frontend (pull_request) Successful in 24s
2024-03-23 23:56:24 +01:00
9de4b06e75 Login 'fixed'
All checks were successful
Build and test backend / Build-backend (pull_request) Successful in 2m53s
Build and test FrontEnd / Build-frontend (pull_request) Successful in 26s
2024-03-23 13:57:46 +01:00
123fa97611 Merge branch 'wal/front/temp' into tonitch/front/fullSpaceApp 2024-03-22 20:15:45 +01:00
1fad792be7 Just to merge 2024-03-22 20:14:20 +01:00
acd1262955 Make app use full space
All checks were successful
Build and test backend / Build-backend (pull_request) Successful in 1m50s
Build and test FrontEnd / Build-frontend (pull_request) Successful in 24s
2024-03-22 13:56:04 +01:00
21 changed files with 836 additions and 102 deletions

View File

@ -16,6 +16,8 @@ repositories {
} }
dependencies { dependencies {
compileOnly("org.projectlombok:lombok")
annotationProcessor("org.projectlombok:lombok")
implementation("org.springframework.boot:spring-boot-starter-jdbc") implementation("org.springframework.boot:spring-boot-starter-jdbc")
implementation("org.springframework.boot:spring-boot-starter-data-jpa") implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.boot:spring-boot-starter-mail") implementation("org.springframework.boot:spring-boot-starter-mail")

View File

@ -0,0 +1,34 @@
package ovh.herisson.Clyde.DTO.Msg;
/******************************************************
* @file DiscussionDTO.java
* @author Anthony Debucquoy
* @scope Extension messagerie
*
* File to format a discussion using messageDTO
******************************************************/
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import lombok.AllArgsConstructor;
import lombok.Data;
import ovh.herisson.Clyde.Tables.User;
import ovh.herisson.Clyde.Tables.Msg.Discussion;
import ovh.herisson.Clyde.DTO.Msg.MessagesDTO;
@Data
@AllArgsConstructor
public class DiscussionDTO {
private long id;
private String name;
private List<User> members;
private List<MessagesDTO> msgs;
public static DiscussionDTO construct(Discussion d, User u){
List<MessagesDTO> msgsdto = new ArrayList<>();
d.getMsgs().forEach(x -> msgsdto.add(MessagesDTO.construct(x, u)));
return new DiscussionDTO(d.getId(), d.getName(), d.getMembers(), msgsdto);
}
}

View File

@ -0,0 +1,33 @@
package ovh.herisson.Clyde.DTO.Msg;
/******************************************************
* @file MessagesDTO.java
* @author Anthony Debucquoy
* @scope Extension messagerie
*
* File to Format the response adding the sender field
******************************************************/
import lombok.AllArgsConstructor;
import lombok.Data;
import ovh.herisson.Clyde.Tables.User;
import ovh.herisson.Clyde.Tables.Msg.Message;
import java.util.Date;
@Data
@AllArgsConstructor
public class MessagesDTO {
private long id;
private String content;
private User author;
private boolean sender;
private Date created;
//TODO: Attachment
public static MessagesDTO construct(Message m, User user){
boolean sender = false;
if(m.getAuthor().equals(user))
sender = true;
return new MessagesDTO(m.getId(), m.getContent(), m.getAuthor(), sender, m.getCreated());
}
}

View File

@ -0,0 +1,126 @@
package ovh.herisson.Clyde.EndPoints.Msg;
/******************************************************
* @file MessagesController.java
* @author Anthony Debucquoy
* @scope Extension messagerie
*
* Entry point for the messages application
******************************************************/
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;
import lombok.AllArgsConstructor;
import ovh.herisson.Clyde.DTO.Msg.DiscussionDTO;
import ovh.herisson.Clyde.Repositories.UserRepository;
import ovh.herisson.Clyde.Repositories.Msg.DiscussionRepository;
import ovh.herisson.Clyde.Responses.UnauthorizedResponse;
import ovh.herisson.Clyde.Services.AuthenticatorService;
import ovh.herisson.Clyde.Services.UserService;
import ovh.herisson.Clyde.Services.Msg.DiscussionService;
import ovh.herisson.Clyde.Tables.User;
import ovh.herisson.Clyde.Tables.Msg.Discussion;
import ovh.herisson.Clyde.Tables.Msg.Message;
@RestController
@CrossOrigin(originPatterns = "*", allowCredentials = "true")
@AllArgsConstructor
public class MessagesController {
private AuthenticatorService authServ;
private DiscussionService discServ;
private DiscussionRepository discRepo;
private UserService userServ;
@GetMapping("/discussions")
public ResponseEntity<Iterable<Discussion>> getDiscussions(@RequestHeader("Authorization") String token ){
User user = authServ.getUserFromToken(token);
if(user == null){
return new UnauthorizedResponse<>(null);
}
Iterable<Discussion> mock = discServ.getOwned(authServ.getUserFromToken(token));
return new ResponseEntity<>(mock, HttpStatus.OK);
}
@GetMapping("/discussion/{id}")
public ResponseEntity<DiscussionDTO> getDiscussion(@RequestHeader("Authorization") String token, @PathVariable long id){
User user = authServ.getUserFromToken(token);
if(user == null || !discServ.hasDiscussion(user, id) ){
return new UnauthorizedResponse<>(null);
}
return new ResponseEntity<>(DiscussionDTO.construct(discRepo.findById(id).orElse(null), authServ.getUserFromToken(token)), HttpStatus.OK);
}
@PatchMapping("/discussion/{id}")
public ResponseEntity<Discussion> AlterDiscussion(@RequestHeader("Authorization") String token, @PathVariable long id, @RequestBody Discussion data){
User user = authServ.getUserFromToken(token);
if(user == null){
return new UnauthorizedResponse<>(null);
}
Discussion disc = discRepo.findById(id).orElse(null);
disc.setName(data.getName());
discRepo.save(disc);
return new ResponseEntity<>(disc, HttpStatus.OK);
}
@PatchMapping("/discussion/{id}/add")
public ResponseEntity<Discussion> invite(@RequestHeader("Authorization") String token, @PathVariable long id, @RequestBody User data){
User user = authServ.getUserFromToken(token);
if(user == null){
return new UnauthorizedResponse<>(null);
}
Discussion disc = discRepo.findById(id).orElse(null);
User invited = userServ.getUserById(data.getRegNo());
disc.addMember(invited);
discRepo.save(disc);
return new ResponseEntity<>(disc, HttpStatus.OK);
}
@PatchMapping("/discussion/{id}/remove")
public ResponseEntity<Discussion> removeMember(@RequestHeader("Authorization") String token, @PathVariable long id, @RequestBody User data){
User user = authServ.getUserFromToken(token);
if(user == null){
return new UnauthorizedResponse<>(null);
}
Discussion disc = discRepo.findById(id).orElse(null);
User member = userServ.getUserById(data.getRegNo());
disc.delMember(member);
discRepo.save(disc);
return new ResponseEntity<>(disc, HttpStatus.OK);
}
@PostMapping("/discussion/{id}")
public ResponseEntity<Discussion> sendMessage(@RequestHeader("Authorization") String token, @PathVariable long id, @RequestBody Message msg){
User user = authServ.getUserFromToken(token);
if(user == null){
return new UnauthorizedResponse<>(null);
}
Discussion disc = discRepo.findById(id).orElse(null);
msg.setAuthor(user);
if(disc != null)
discServ.CreateMessage(disc, msg);
return new ResponseEntity<>(disc, HttpStatus.OK);
}
@PostMapping("/discussion")
public ResponseEntity<Discussion> createDiscussion(@RequestHeader("Authorization") String token, @RequestBody Discussion data){
return new ResponseEntity<>(discServ.create(data.getName(), authServ.getUserFromToken(token)), HttpStatus.OK);
}
}

View File

@ -0,0 +1,23 @@
package ovh.herisson.Clyde.Repositories.Msg;
/******************************************************
* @file DiscussionRepository.java
* @author Anthony Debucquoy
* @scope Extension messagerie
*
* Repository of Discussion allowing to fetch discussion by user
******************************************************/
import java.util.List;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import ovh.herisson.Clyde.Tables.Msg.Discussion;
public interface DiscussionRepository extends CrudRepository<Discussion, Long>{
@Query("SELECT d FROM Discussion d INNER JOIN FETCH d.members dm WHERE dm.id = ?1")
List<Discussion> findByMembership(long userid);
}

View File

@ -0,0 +1,14 @@
package ovh.herisson.Clyde.Repositories.Msg;
/******************************************************
* @file MessageRepository.java
* @author Anthony Debucquoy
* @scope Extension messagerie
******************************************************/
import org.springframework.data.repository.CrudRepository;
import ovh.herisson.Clyde.Tables.Msg.Message;
public interface MessageRepository extends CrudRepository<Message, Long> {}

View File

@ -0,0 +1,57 @@
package ovh.herisson.Clyde.Services.Msg;
import java.util.List;
/******************************************************
* @file DiscussionService.java
* @author Anthony Debucquoy
* @scope Extension messagerie
*
* Various function utilised by the messages application
******************************************************/
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.fasterxml.jackson.databind.util.JSONPObject;
import ovh.herisson.Clyde.Repositories.Msg.DiscussionRepository;
import ovh.herisson.Clyde.Tables.User;
import ovh.herisson.Clyde.Tables.Msg.Discussion;
import ovh.herisson.Clyde.Tables.Msg.Message;
@Service
public class DiscussionService {
@Autowired
private DiscussionRepository discRepo;
public Discussion create(String name, User author){
return discRepo.save(new Discussion(name, author));
}
/**
* list discussions owned by a certain user
*/
public Iterable<Discussion> getOwned(User author){
return discRepo.findByMembership(author.getRegNo());
}
/**
* Create a message and link it to it's discussion
*/
public Discussion CreateMessage(Discussion disc, Message msg){
disc.addMessage(msg);
return discRepo.save(disc);
}
/**
* Check if a user is in a discussion
*/
public boolean hasDiscussion(User user, long id) {
Discussion disc = discRepo.findById(id).orElse(null);
List<User> members = disc.getMembers();
return members.contains(user);
}
}

View File

@ -0,0 +1,72 @@
package ovh.herisson.Clyde.Tables.Msg;
/******************************************************
* @file Discussion.java
* @author Anthony Debucquoy
* @scope Extension messagerie
*
* Discussion allow to regroupe multiple user in and message together
* for the messages application to work
******************************************************/
import java.util.List;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.JoinTable;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.OneToMany;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import ovh.herisson.Clyde.Tables.User;
@Entity
@Getter
@Setter
@NoArgsConstructor
public class Discussion{
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String name;
@ManyToMany
@JoinTable(
name = "discussion_members",
joinColumns = @JoinColumn(name = "discussion_id"),
inverseJoinColumns = @JoinColumn(name = "user_id")
)
private List<User> members;
@OneToMany(mappedBy="discussion", orphanRemoval = true, cascade = CascadeType.ALL)
private List<Message> msgs;
public Discussion(String name){
this.name = name;
}
public Discussion(String name, User user){
this.name = name;
this.members = List.of(user);
}
public void addMessage(Message msg){
msg.setDiscussion(this);
msgs.add(msg);
}
public void addMember(User user) {
members.add(user);
}
public void delMember(User user) {
members.remove(user);
}
}

View File

@ -0,0 +1,66 @@
package ovh.herisson.Clyde.Tables.Msg;
/******************************************************
* @file Message.java
* @author Anthony Debucquoy
* @scope Extension messagerie
*
* Represent a message sent to a discussion
******************************************************/
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import com.fasterxml.jackson.annotation.JsonIgnore;
import java.util.Date;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.OneToOne;
import jakarta.persistence.PrePersist;
import jakarta.persistence.Temporal;
import jakarta.persistence.TemporalType;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import ovh.herisson.Clyde.Tables.User;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class Message {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String content;
@CreationTimestamp
@Column(nullable = false)
private Date created;
@ManyToOne
private User author;
public User getAuthor() {
return author;
}
@OneToOne
private Message response;
@ManyToOne(optional = false)
@JsonIgnore
private Discussion discussion;
}

View File

@ -1,7 +1,11 @@
package ovh.herisson.Clyde.Tables; package ovh.herisson.Clyde.Tables;
import jakarta.persistence.*; import jakarta.persistence.*;
import ovh.herisson.Clyde.Tables.Msg.Discussion;
import ovh.herisson.Clyde.Tables.Msg.Message;
import java.util.Date; import java.util.Date;
import java.util.List;
@Entity @Entity
@ -20,6 +24,15 @@ public class User {
private String profilePictureUrl; private String profilePictureUrl;
private ovh.herisson.Clyde.Tables.Role role; private ovh.herisson.Clyde.Tables.Role role;
private String password; private String password;
////// Extension Messagerie /////
@OneToMany(mappedBy = "author", cascade = CascadeType.ALL)
private List<Message> msgs;
/////////////////////////////////
@ManyToMany( mappedBy = "members" )
private List<Discussion> discussions;
public User(String lastName, String firstName, String email, String address, public User(String lastName, String firstName, String email, String address,
String country, Date birthDate, String profilePictureUrl, Role role, String password) String country, Date birthDate, String profilePictureUrl, Role role, String password)
{ {

View File

@ -52,7 +52,7 @@ window.addEventListener('hashchange', () => {
<a class="icon" href="#Notifications"> <a class="icon" href="#Notifications">
<div class="fa-solid fa-bell" style="margin-top: 7px; margin-bottom: 3px;"></div> <div class="fa-solid fa-bell" style="margin-top: 7px; margin-bottom: 3px;"></div>
</a></li> </a></li>
<li @click="active=!active" class="option"style="float: right;" title=settings> <li @click="active=!active" class="option"style="float: right;" title=settings>
<a class="icon" > <a class="icon" >
<div class="fa-solid fa-gear" style="margin-top: 7px; margin-bottom: 3px;"></div> <div class="fa-solid fa-gear" style="margin-top: 7px; margin-bottom: 3px;"></div>
<div v-if="active" class="dropdown"> <div v-if="active" class="dropdown">
@ -86,12 +86,9 @@ window.addEventListener('hashchange', () => {
</ul> </ul>
</div> </div>
<div class="page"> <div class="page">
<div style=" margin:50px;">
<Suspense> <Suspense>
<component :is="currentView" />
<component :is="currentView" />
</Suspense> </Suspense>
</div>
</div> </div>
</div> </div>
</template> </template>
@ -99,6 +96,8 @@ window.addEventListener('hashchange', () => {
<style scoped> <style scoped>
.container{ .container{
height: 100%;
width: 100%;
display:grid; display:grid;
grid-template-columns:[firstCol-start]70px[firstCol-end secondCol-start]auto[endCol]; grid-template-columns:[firstCol-start]70px[firstCol-end secondCol-start]auto[endCol];
grid-template-rows:[firstRow-start]61px[firstRow-end secondRow-start] auto [endRow]; grid-template-rows:[firstRow-start]61px[firstRow-end secondRow-start] auto [endRow];
@ -111,6 +110,8 @@ window.addEventListener('hashchange', () => {
.page { .page {
grid-area:page; grid-area:page;
height: 100%;
width: 100%;
place-self:center; place-self:center;
} }

View File

@ -16,10 +16,9 @@
<template> <template>
<div v-for="item of requests"> <div style='display:flex; justify-content:center; min-width:1140px;' v-for="item of requests">
<div class="bodu" v-if="item.state === 'Pending'"> <div class="bodu" v-if="item.state === 'Pending'">
<div class="container"> <div class="container">
<div class="id"><a>{{item.id}}</a></div> <div class="id"><a>{{item.id}}</a></div>
<div class="surname"><a>{{item.lastName}}</a></div> <div class="surname"><a>{{item.lastName}}</a></div>
<div class="firstname"><a>{{item.firstName}}</a></div> <div class="firstname"><a>{{item.firstName}}</a></div>
@ -37,10 +36,9 @@
height:100px; height:100px;
font-size:20px; font-size:20px;
display:grid; display:grid;
grid-template-columns:[firstCol-start]100px[firstCol-end secondCol-start]150px[secondCol-end thirdCol-start]200px[thirdCol-end fourthCol-start]150px[fourthCol-end]150px[fifthCol-end]150px[sixthCol-end]150px[endCol]; grid-template-columns:10% 14.2% 19% 14.2% 14.2% 14.2% 14.2%;
grid-template-areas: grid-template-areas:
"id type surname firstname infos accept refuse"; "id type surname firstname infos accept refuse";
column-gap:10px;
} }
@ -99,9 +97,9 @@
} }
.bodu { .bodu {
width:100%; margin-top:2%;
margin-bottom:10px; width:66%;
border:2px solid black; border:2px solid black;
border-radius:9px; border-radius:9px;
background-color:rgb(50,50,50); background-color:rgb(50,50,50);

View File

@ -56,10 +56,10 @@
<template> <template>
<div class='loginBox'> <div class="setup">
<div v-if="loginPage"> <div v-if="loginPage">
<form @submit.prevent=" login(outputs.email,outputs.password);goBackHome();"class="form"> <div class='loginBox' style="margin-top:30%;">
<form @submit.prevent="login(outputs.email,outputs.password);goBackHome();"class="form">
<h1 style="color:rgb(239,60,168); font-family: sans-serif;"> <h1 style="color:rgb(239,60,168); font-family: sans-serif;">
{{i18n("login.guest.signin")}} {{i18n("login.guest.signin")}}
</h1> </h1>
@ -74,14 +74,15 @@
<div class="register"> <div class="register">
<a @click="loginPage=!loginPage">{{i18n("login.guest.register")}}</a> <a @click="loginPage=!loginPage">{{i18n("login.guest.register")}}</a>
</div> </div>
<div class="inputBox"> <div class="inputBox" style="margin-bottom:35px;">
<input type="submit" v-model="submitValue"> <input type="submit" v-model="submitValue">
</div> </div>
</form> </form>
</div> </div>
</div>
<div v-else> <div v-else>
<form class="form"> <div class='loginBox' style="margin-top:30%; margin-bottom:50%;">
<form class="form">
<h1 style="color:rgb(239,60,168); font-family: sans-serif; text-align:center;"> <h1 style="color:rgb(239,60,168); font-family: sans-serif; text-align:center;">
{{i18n("login.guest.welcome")}} {{i18n("login.guest.welcome")}}
</h1> </h1>
@ -128,9 +129,12 @@
<p>{{i18n("login.guest.country")}}</p> <p>{{i18n("login.guest.country")}}</p>
<input type="text" v-model="outputs.country"> <input type="text" v-model="outputs.country">
</div> </div>
<form novalidate enctype="multipart/form-data" class="inputBox"> <form class="inputBox"novalidate enctype="multipart/form-data">
<p>{{i18n("profile.picture").toUpperCase()}}</p> <p>{{i18n("profile.picture").toUpperCase()}}</p>
<input type="file" :disabled="imageSaved" @change="ppData = uploadProfilePicture($event.target.files); imageSaved = true;" accept="image/*"> <label class="browser">
Parcourir . . .
<input type="file" :disabled="imageSaved" @change="ppData = uploadProfilePicture($event.target.files); imageSaved = true;" accept="image/*">
</label>
</form> </form>
<div class="inputBox"> <div class="inputBox">
<p>{{i18n("Curriculum").toUpperCase()}}</p> <p>{{i18n("Curriculum").toUpperCase()}}</p>
@ -153,35 +157,26 @@
</div> </div>
</form> </form>
</div> </div>
</div> </div>
</div>
</template> </template>
<style scoped> <style scoped>
.Home{
position:absolute;
display: flex;
z-index: 100;
padding: 8px 16px;
color:rgb(255, 255, 255);
text-decoration: none;
}
.Home:hover{ .setup {
width:40px; margin-left: auto;
background-color: black; margin-right:auto;
border-radius:6px; min-width:400px;
color:white;
transform: translate(0px ,1px);
}
width:25%;
height:60%;
}
.loginBox { .loginBox {
background-color: rgb(24,24,24); background-color: rgb(24,24,24);
width: 400px;
display:flex; display:flex;
justify-content: center; justify-content: center;
padding: 40px; border-radius: 5%;
border-radius: 20px;
box-shadow:0 5px 25px #000000; box-shadow:0 5px 25px #000000;
} }
@ -190,9 +185,8 @@
width:100%; width:100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center;
align-items:center; align-items:center;
gap: 15px; gap: 3%;
} }
@ -200,12 +194,12 @@
width:100%; width:100%;
border: none; border: none;
margin-right: 50px; margin-right: 12.5%;
padding-left: 10px; padding-left: 2.5%;
padding-top:10px; padding-top:2.5%;
padding-bottom:10px; padding-bottom:2.5%;
outline:none; outline:none;
border-radius: 4px; border-radius: 10px;
font-size:1.35em; font-size:1.35em;
} }
@ -220,8 +214,9 @@
.register{ .register{
color:rgb(239,60,168); color:rgb(239,60,168);
width: 100%; width:70%;
display:flex; margin-bottom:20px;
margin-top:20px;
cursor: pointer; cursor: pointer;
} }
@ -250,6 +245,21 @@ input[type=submit],button,select{
} }
input[type=file]{
display:none;
}
.browser{
display:inline-block;
cursor:pointer;
border-radius:20px;
background-color:rgb(239,60,168);
padding:5%;
font-size:1.35em;
font-family:sans-serif;
background:#FFFFFF;
}
button:active ,.switchpage:active{ button:active ,.switchpage:active{
opacity:0.8; opacity:0.8;

View File

@ -105,7 +105,8 @@
</button> </button>
</div> </div>
<div v-if="createMod"> <div v-if="createMod">
<form class="listElement"> <form class="listElement" style="width:40%;margin-right:auto;margin-left:auto;">
<div style="margin-bottom:20px;"> <div style="margin-bottom:20px;">
{{i18n("name")}} : {{i18n("name")}} :
<input v-model="toAdd.title"> <input v-model="toAdd.title">
@ -125,7 +126,7 @@
</form> </form>
</div> </div>
<div v-if="deleteMod"> <div v-if="deleteMod">
<form class="listElement"> <form class="listElement" style="width:40%;margin-right:auto;margin-left:auto;">
<div style="margin-bottom:20px;"> <div style="margin-bottom:20px;">
{{i18n("courses.toDelete")}} : {{i18n("courses.toDelete")}} :
<select style="max-width:200px;" class="teacher" v-model="toRemove"> <select style="max-width:200px;" class="teacher" v-model="toRemove">
@ -138,7 +139,7 @@
</form> </form>
</div> </div>
<div v-if="!createMod && !deleteMod" v-for="item in curriculum" :key="item.title"> <div v-if="!createMod && !deleteMod" v-for="item in curriculum" :key="item.title" style="width:50%;margin-left:auto; margin-right:auto;">
<div v-if="editElementID !== item.title" style ="padding:15px 15px 15px 15px;"> <div v-if="editElementID !== item.title" style ="padding:15px 15px 15px 15px;">
<button @click="editElementID = item.title; setModify(item); "> <button @click="editElementID = item.title; setModify(item); ">
{{i18n("courses.modify")}} {{i18n("courses.modify")}}
@ -148,7 +149,8 @@
<button @click="editElementID= '';patchCourse(item)"> {{i18n("courses.confirm")}} </button> <button @click="editElementID= '';patchCourse(item)"> {{i18n("courses.confirm")}} </button>
<button @click="editElementID= '';"> {{i18n("courses.back")}} </button> <button @click="editElementID= '';"> {{i18n("courses.back")}} </button>
</div> </div>
<div class="listElement" > <div class="listElement" >
<div class="containerElement" v-if="editElementID !== item.title" > <div class="containerElement" v-if="editElementID !== item.title" >
<div class="name"> {{item.title}} </div> <div class="name"> {{item.title}} </div>
@ -172,17 +174,27 @@
<style scoped> <style scoped>
.body { .body {
width:100%; width:100%;
margin-bottom:10px; margin-top:3.5%;
} }
.infosContainer {
min-width:350px;
padding-bottom:50px;
border:2px solid black;
font-size:25px;
color:white;
padding:20px;
background-color:rgb(50,50,50);
border-radius:20px;
}
.containerElement{ .containerElement{
justify-content:center; justify-content:center;
display:grid; display:grid;
grid-template-columns:350px 350px 200px; grid-template-columns:38.8% 38.8% 22.4%;
grid-template-areas: grid-template-areas:
"name teacher credits"; "name teacher credits";
column-gap:10px; column-gap:10px; }
}
.name { .name {
grid-area:name; grid-area:name;
@ -200,6 +212,7 @@
} }
.listElement{ .listElement{
min-width:625px;
border:2px solid black; border:2px solid black;
font-size:25px; font-size:25px;
color:white; color:white;
@ -207,6 +220,7 @@
background-color:rgb(50,50,50); background-color:rgb(50,50,50);
border-radius:20px; border-radius:20px;
margin-bottom:10px; margin-bottom:10px;
} }
.modify{ .modify{
@ -255,10 +269,11 @@
} }
.listTitle{ .listTitle{
min-width:380px;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
width:400px; width:25%;
margin-left:auto; margin-left:auto;
margin-right:auto; margin-right:auto;
border:2px solid black; border:2px solid black;
@ -266,7 +281,8 @@
color:white; color:white;
padding:20px; padding:20px;
background-color:rgb(50,50,50); background-color:rgb(50,50,50);
border-radius:20px;margin-bottom:10px; border-radius:20px;
margin-bottom:10px;
button:hover{ button:hover{
opacity:0.8; opacity:0.8;

213
frontend/src/Apps/Msg.vue Normal file
View File

@ -0,0 +1,213 @@
<!----------------------------------------------------
File: Msg.vue
Author: Anthony Debucquoy
Scope: Extension messagerie
Description: Main msg page
----------------------------------------------------->
<script setup>
import { ref, reactive } from 'vue'
import { discussionsList, currentDiscussion, fetchDiscussion, createDiscussion, sendMessage, updateDiscussionName, invite, removeMember} from '@/rest/msg.js'
const msgContent = ref("");
const addMember = ref(false);
const currentTitle = ref("");
function formatTime(date){
return date.getHours() + ":" + date.getMinutes() + " " + date.getDate() + "/" + date.getMonth();
}
</script>
<template>
<div id="msg">
<div id="discList">
<div @click="fetchDiscussion(discussion.id).then(e => currentTitle = currentDiscussion.name)" class="discItem" v-for="discussion in discussionsList" :key="discussion.id">{{ discussion.name }}</div>
<button id="createDiscussion" @click="createDiscussion('New Discussion')">+</button>
</div>
<div id="discussion" v-if="currentDiscussion.length != 0">
<h1 id=msgName ><input class="InputTitle" type="text" @change="updateDiscussionName(currentDiscussion.id, currentTitle)" v-model="currentTitle"></h1>
<div id=msgs>
<div class="msg" v-for="msg in currentDiscussion.msgs" :sender="msg.sender" :key="msg.id">
{{ msg.content }}<br/>
<span class="sender"><span v-if="!msg.sender">{{ msg.author.firstName }} {{ msg.author.lastName.toUpperCase() }}</span> {{formatTime(new Date(msg.created))}}</span>
</div>
</div>
<div id=messageBox>
<input type="text" @keyup.enter="sendMessage(currentDiscussion.id, msgContent, null); msgContent = ''" v-model="msgContent">
<input type="submit" @click="sendMessage(currentDiscussion.id, msgContent, null); msgContent = ''" value="send">
</div>
</div>
<div id="members" v-if="currentDiscussion.length != 0">
<div class="memberItem" v-for="member in currentDiscussion.members" @click="removeMember(currentDiscussion.id, member.regNo)" :key="member.id"><span>{{ member.firstName }} {{ member.lastName.toUpperCase() }}</span></div>
<input type=text id="addMembers" @focus="addMember = true" @blur="addMember = false;$event.target.value = ''" @change="invite(currentDiscussion.id, $event.target.value)" :placeholder="addMember ? 'Regno' : '+'"/>
</div>
</div>
</template>
<style scoped>
div#msg{
position: relative;
width: 100%;
height: 100%;
display: grid;
grid-template-columns: 20% auto 10%;
}
div#discList{
margin: 30px 0 30px 30px;
background-color: rgba(255, 255, 255, 0.05);
border-radius: 10px;
overflow: hidden;
padding: 10px;
display: flex;
flex-direction: column;
}
div#members{
margin: 30px 0;
border-radius: 10px 0 0 10px;
background-color: red;
background-color: rgba(255, 255, 255, 0.05);
overflow: hidden;
display: flex;
padding: 10px 0 0 10px;
flex-direction: column;
}
.InputTitle{
all: inherit;
margin: auto;
}
.discItem{
color: darkorange;
display: flex;
font-family: sans-serif;
font-weight: bold;
height: 4vh;
margin: 5px;
border-radius: 0 30px 30px 0;
align-items: center;
justify-content: center;
border: 1px solid darkorange;
}
.memberItem{
color: darkorange;
display: flex;
font-family: sans-serif;
font-weight: bold;
height: 4vh;
margin: 5px;
border-radius: 30px 0 0 30px;
align-items: center;
justify-content: center;
border: 1px solid darkorange;
}
.memberItem:hover span{
display: none;
}
.memberItem:hover{
background-color: red;
}
.memberItem:hover:before{
color: white;
content: "X"
}
#createDiscussion{
height: 4vh;
margin: 5px;
color: white;
background-color: green;
border-radius: 0 30px 30px 0;
border: none;
font-weight: 900;
font-size: 2em;
}
#addMembers{
height: 4vh;
margin: 5px;
text-align: center;
color: white;
background-color: green;
border-radius: 30px 0 0 30px;
border: none;
font-weight: 900;
font-size: 2em;
}
div#discussion{
display: flex;
flex-direction: column;
margin: 30px;
background-color: rgba(255, 255, 255, 0.05);
border-radius: 10px;
overflow: hidden;
}
#msgName{
text-align: center;
display: block;
background-color: #2a1981;
border-radius: 5px;
color: white;
width: 75%;
margin: 30px auto;
}
.discItem:hover{
background-color: gray;
}
#msgs{
display: flex;
flex-grow: 1;
flex-direction: column;
}
.msg {
background-color: aliceblue;
font-family: sans-serif;
margin: 10px;
padding: 5px;
border-radius: 3px;
max-width: 50%;
align-self: start;
}
.sender{
display: inline-block;
color: gray;
font-size: 0.5em;
}
.msg[sender=true]{
background-color: darkorange;
align-self: end;
}
#messageBox{
width: 100%;
height: 30px;
background-color: white;
display: flex;
}
#messageBox input[type="text"]{
all: inherit;
padding: 0 10px;
}
#messageBox input[type="submit"]{
border: inherit;
padding: 0 10px;
}
</style>

View File

@ -210,11 +210,11 @@
<style scoped> <style scoped>
.container{ .container{
min-width:675px;
display:grid; display:grid;
grid-template-columns:200px 900px; grid-template-columns:10vw 50vw;
grid-template-rows:200px auto; grid-template-rows:200px auto;
column-gap:30px; column-gap:2.7%;
row-gap:45px; row-gap:45px;
grid-template-areas: grid-template-areas:
"profilPic globalInfos" "profilPic globalInfos"
@ -222,6 +222,7 @@
} }
.profilPic{ .profilPic{
width:100%;
grid-area:profilPic; grid-area:profilPic;
} }
@ -242,13 +243,17 @@
grid-area:minfos; grid-area:minfos;
} }
.body { .body {
min-width:960px;
width:100%; width:100%;
margin-bottom:10px; display:flex;
align-items:center;
justify-content:center;
margin-top:5%;
} }
.containerElement{ .containerElement{
justify-content:center; justify-content:center;
display:grid; display:grid;
grid-template-columns:350px 350px 200px; grid-template-columns:38.8% 38.8% 22.4%;
grid-template-areas: grid-template-areas:
"name teacher credits"; "name teacher credits";
column-gap:10px; column-gap:10px;
@ -271,10 +276,11 @@
} }
.listTitle{ .listTitle{
min-width:197px;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
width:200px; width:8vw;
margin-left:auto; margin-left:auto;
margin-right:auto; margin-right:auto;
border:2px solid black; border:2px solid black;
@ -286,6 +292,7 @@
} }
.listElement{ .listElement{
min-width:625px;
border:2px solid black; border:2px solid black;
font-size:25px; font-size:25px;
color:white; color:white;
@ -296,6 +303,7 @@
} }
.infosContainer { .infosContainer {
min-width:350px;
padding-bottom:50px; padding-bottom:50px;
border:2px solid black; border:2px solid black;
font-size:25px; font-size:25px;

View File

@ -5,8 +5,8 @@
const users = await getStudents(); const users = await getStudents();
</script> </script>
<template> <template style="margin-top:5%;">
<div v-for="item in users"> <div style="display:flex; justify-content:center; " v-for="item in users">
<div class="bodu"> <div class="bodu">
<div class="container"> <div class="container">
<div class="status"><a style="margin-left:30px">{{item.status}}</a></div> <div class="status"><a style="margin-left:30px">{{item.status}}</a></div>
@ -25,10 +25,9 @@
height:100px; height:100px;
font-size:30px; font-size:30px;
display:grid; display:grid;
grid-template-columns:250px 250px 250px 250px 150px; grid-template-columns:21.7% 21.7% 21.7% 21.7% 13.1%;
grid-template-areas: grid-template-areas:
"status option surname firstname infos"; "status option surname firstname infos";
column-gap:10px;
} }
@ -42,21 +41,6 @@
align-self:center; align-self:center;
} }
.refuse{
grid-area:refuse;
align-self:center;
}
.titles {
grid-area:titles;
background-color:rgb(215,215,215);
}
.id{
grid-area:id;
margin-left:40px;
align-self:center;
}
.status{ .status{
grid-area:status; grid-area:status;
align-self:center; align-self:center;
@ -81,15 +65,15 @@
button{ button{
font-size:15px; font-size:15px;
height:50px; height:50px;
width:100px; width:75%;
border:none; border:none;
border-radius:20px; border-radius:20px;
} }
.bodu { .bodu {
width:100%; margin-top:2%;
margin-bottom:10px; width:66%;
border:2px solid black; border:2px solid black;
border-radius:9px; border-radius:9px;
background-color:rgb(50,50,50); background-color:rgb(50,50,50);

View File

@ -7,8 +7,8 @@
const users = await getAllUsers(); const users = await getAllUsers();
</script> </script>
<template> <template style="margin-top:5%;">
<div v-for="item in users"> <div style="display:flex; justify-content:center; min-width:1140px;" v-for="item in users">
<div class="bodu"> <div class="bodu">
<div class="container"> <div class="container">
<div class="role"><a style="margin-left:30px">{{i18n(item.role)}}</a></div> <div class="role"><a style="margin-left:30px">{{i18n(item.role)}}</a></div>
@ -22,23 +22,20 @@
<style scoped> <style scoped>
.container{ .container{
justify-content:center;
align-items:center;
color:white; color:white;
height:100px; height:100px;
font-size:30px; font-size:30px;
display:grid; display:grid;
grid-template-columns:250px 250px 250px 150px; grid-template-columns:27.7% 27.7% 27.7% 16.9%;
grid-template-areas: grid-template-areas:
"role surname firstname infos"; "role surname firstname infos";
column-gap:10px;
} }
.infos { .infos {
grid-area:infos; grid-area:infos;
align-items:center; align-self:center;
} }
.role { .role {
@ -67,20 +64,18 @@
button{ button{
font-size:15px; font-size:15px;
height:50px; height:50px;
width:100px; width:75%;
border:none; border:none;
border-radius:20px; border-radius:20px;
} }
.bodu { .bodu {
width:100%; margin-top:2%;
margin-bottom:10px; width:66%;
border:2px solid black; border:2px solid black;
border-radius:9px; border-radius:9px;
background-color:rgb(50,50,50); background-color:rgb(50,50,50);
} }
</style> </style>

View File

@ -1,4 +1,11 @@
body { body {
background-color: rgb(53, 25, 60); background-color: rgb(53, 25, 60);
margin:0; margin:0;
width: 100vw;
height: 100vh;
}
#app {
width: 100%;
height: 100%;
} }

View File

@ -9,6 +9,7 @@ import Profil from "@/Apps/Profil.vue"
import Courses from "@/Apps/ManageCourses.vue" import Courses from "@/Apps/ManageCourses.vue"
import Users from "@/Apps/UsersList.vue" import Users from "@/Apps/UsersList.vue"
import Students from "@/Apps/StudentsList.vue" import Students from "@/Apps/StudentsList.vue"
import Msg from "@/Apps/Msg.vue"
const apps = { const apps = {
'/login': LoginPage, '/login': LoginPage,
@ -17,6 +18,7 @@ const apps = {
'/manage-courses' : Courses, '/manage-courses' : Courses,
'/users-list' : Users, '/users-list' : Users,
'/students-list' : Students, '/students-list' : Students,
'/msg' : Msg,
} }
const appsList = { const appsList = {

60
frontend/src/rest/msg.js Normal file
View File

@ -0,0 +1,60 @@
/*******************************************************
* File: msg.js
* Author: Anthony Debucquoy
* Scope: Extension messagerie
* Description: Messages frontend api consumer
*******************************************************/
import { restGet, restPost, restPatch } from './restConsumer.js'
import { ref } from 'vue'
/**
* - id
* - name
* - members
*/
export const discussionsList = ref();
export const currentDiscussion = ref([]);
let timerSet = false
export async function createDiscussion(name){
let disc = await restPost("/discussion", {name: name});
discussionsList.value.push(disc);
}
export async function invite(id, regNo){
restPatch("/discussion/"+ id+ "/add", {regNo: parseInt(regNo)}).then(() => fetchDiscussion(id))
}
export async function removeMember(id, regNo){
restPatch("/discussion/"+ id+ "/remove", {regNo: parseInt(regNo)}).then(() => fetchDiscussion(id))
}
export async function sendMessage(id, content, responseId){
let data = {
content: content,
response: responseId,
}
restPost("/discussion/" + id, data).then(() => fetchDiscussion(id));
}
export async function updateDiscussionName(id, name){
restPatch("/discussion/" + id, {name: name}).then(() => fetchDiscussions());
}
async function fetchDiscussions(){
discussionsList.value = await restGet("/discussions");
}
export async function fetchDiscussion(id){
currentDiscussion.value = await restGet("/discussion/" + id);
if(!timerSet){
timerSet = true;
setTimeout(() => {timerSet = false;fetchDiscussion(currentDiscussion.value.id)} , 5000);
}
}
await fetchDiscussions();