This pull request adds the members to the team with the possibilitie to add or remove a player or quit the team. It means that this PR implements the account within the team's part of the application. Moreover some bugs has been fixed. The view of the team has been translated to react.js This PR also adds the edit and delete of a team. Co-authored-by: mael.daim <Mael.DAIM@etu.uca.fr> Co-authored-by: maxime <maximebatista18@gmail.com> Reviewed-on: #84 Co-authored-by: Maël DAIM <mael.daim@etu.uca.fr> Co-committed-by: Maël DAIM <mael.daim@etu.uca.fr>pull/92/head
parent
939a611e45
commit
3a437a7ad1
@ -0,0 +1,19 @@
|
||||
import {User} from "./User";
|
||||
|
||||
export interface TeamInfo {
|
||||
id: number
|
||||
name: string
|
||||
picture: string
|
||||
mainColor: string
|
||||
secondColor: string
|
||||
}
|
||||
|
||||
export interface Team {
|
||||
info: TeamInfo
|
||||
members: Member[]
|
||||
}
|
||||
|
||||
export interface Member {
|
||||
user: User
|
||||
role: string
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
export interface User {
|
||||
id: number
|
||||
name: string
|
||||
email: string
|
||||
profilePicture: string
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import { Pos } from "../components/arrows/Pos"
|
||||
import { Segment } from "../components/arrows/BendableArrow"
|
||||
import { Pos } from "../../components/arrows/Pos"
|
||||
import { Segment } from "../../components/arrows/BendableArrow"
|
||||
import { PlayerId } from "./Player"
|
||||
|
||||
export enum ActionKind {
|
@ -1,14 +1,17 @@
|
||||
import { Team } from "./Team"
|
||||
|
||||
export type PlayerId = string
|
||||
|
||||
export enum PlayerTeam {
|
||||
Allies = "allies",
|
||||
Opponents = "opponents",
|
||||
}
|
||||
|
||||
export interface Player {
|
||||
readonly id: PlayerId
|
||||
|
||||
/**
|
||||
* the player's team
|
||||
* */
|
||||
readonly team: Team
|
||||
readonly team: PlayerTeam
|
||||
|
||||
/**
|
||||
* player's role
|
@ -1,5 +1,5 @@
|
||||
import { Player } from "./Player"
|
||||
import { CourtObject } from "./CourtObjects"
|
||||
import { CourtObject } from "./Ball"
|
||||
import { Action } from "./Action"
|
||||
|
||||
export interface Tactic {
|
@ -0,0 +1,135 @@
|
||||
#main-div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#main-div header {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
background-color: #525252;
|
||||
width: 100%;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
header h1 a {
|
||||
color: orange;
|
||||
text-decoration: none;
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
.square {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border: 2px white solid;
|
||||
}
|
||||
|
||||
#team-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 60%;
|
||||
background-color: #8f8f8f;
|
||||
padding-bottom: 10px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
#first-part {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#team-name {
|
||||
font-size: 2.8em;
|
||||
}
|
||||
|
||||
#colors {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.color {
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
#colorsTitle {
|
||||
width: 110%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
font-size: 1.3em;
|
||||
color: white;
|
||||
}
|
||||
|
||||
#actual-colors {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
#logo {
|
||||
aspect-ratio: 3/2;
|
||||
object-fit: contain;
|
||||
max-width: 70%;
|
||||
max-height: 70%;
|
||||
}
|
||||
|
||||
#delete {
|
||||
border-radius: 10px;
|
||||
background-color: red;
|
||||
color: white;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
#edit {
|
||||
border-radius: 10px;
|
||||
background-color: orange;
|
||||
color: white;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
#head-members {
|
||||
width: 33%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
|
||||
#add-member {
|
||||
height: 30px;
|
||||
aspect-ratio: 1/1;
|
||||
border-radius: 100%;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
#members {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #bcbcbc;
|
||||
width: 60%;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.member {
|
||||
width: 60%;
|
||||
background-color: white;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-evenly;
|
||||
align-items: center;
|
||||
border-radius: 10px;
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
#profile-picture {
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
export enum Team {
|
||||
Allies = "allies",
|
||||
Opponents = "opponents",
|
||||
}
|
@ -0,0 +1,169 @@
|
||||
import "../style/team_panel.css"
|
||||
import { BASE } from "../Constants"
|
||||
import { Team, TeamInfo, Member } from "../model/Team"
|
||||
import { User } from "../model/User"
|
||||
|
||||
export default function TeamPanel({
|
||||
isCoach,
|
||||
team,
|
||||
currentUserId,
|
||||
}: {
|
||||
isCoach: boolean
|
||||
team: Team
|
||||
currentUserId: number
|
||||
}) {
|
||||
return (
|
||||
<div id="main-div">
|
||||
<header>
|
||||
<h1>
|
||||
<a href={BASE + "/"}>IQBall</a>
|
||||
</h1>
|
||||
</header>
|
||||
<TeamDisplay team={team.info} />
|
||||
|
||||
{isCoach && <CoachOptions id={team.info.id} />}
|
||||
|
||||
<MembersDisplay
|
||||
members={team.members}
|
||||
isCoach={isCoach}
|
||||
idTeam={team.info.id}
|
||||
currentUserId={currentUserId}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function TeamDisplay({ team }: { team: TeamInfo }) {
|
||||
return (
|
||||
<div id="team-info">
|
||||
<div id="first-part">
|
||||
<h1 id="team-name">{team.name}</h1>
|
||||
<img id="logo" src={team.picture} alt="Logo d'équipe" />
|
||||
</div>
|
||||
<div id="colors">
|
||||
<div id="colorsTitle">
|
||||
<p>Couleur principale</p>
|
||||
<p>Couleur secondaire</p>
|
||||
</div>
|
||||
<div id="actual-colors">
|
||||
<ColorDisplay color={team.mainColor} />
|
||||
<ColorDisplay color={team.secondColor} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function ColorDisplay({ color }: { color: string }) {
|
||||
return <div className="square" style={{ backgroundColor: color }} />
|
||||
}
|
||||
|
||||
function CoachOptions({ id }: { id: number }) {
|
||||
return (
|
||||
<div>
|
||||
<button
|
||||
id="delete"
|
||||
onClick={() =>
|
||||
confirm("Êtes-vous sûr de supprimer cette équipe?")
|
||||
? (window.location.href = `${BASE}/team/${id}/delete`)
|
||||
: {}
|
||||
}>
|
||||
Supprimer
|
||||
</button>
|
||||
<button
|
||||
id="edit"
|
||||
onClick={() =>
|
||||
(window.location.href = `${BASE}/team/${id}/edit`)
|
||||
}>
|
||||
Modifier
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function MembersDisplay({
|
||||
members,
|
||||
isCoach,
|
||||
idTeam,
|
||||
currentUserId,
|
||||
}: {
|
||||
members: Member[]
|
||||
isCoach: boolean
|
||||
idTeam: number
|
||||
currentUserId: number
|
||||
}) {
|
||||
const listMember = members.map((member) => (
|
||||
<MemberDisplay
|
||||
member={member}
|
||||
isCoach={isCoach}
|
||||
idTeam={idTeam}
|
||||
currentUserId={currentUserId}
|
||||
/>
|
||||
))
|
||||
return (
|
||||
<div id="members">
|
||||
<div id="head-members">
|
||||
<h2>Membres :</h2>
|
||||
{isCoach && (
|
||||
<button
|
||||
id="add-member"
|
||||
onClick={() =>
|
||||
(window.location.href = `${BASE}/team/${idTeam}/addMember`)
|
||||
}>
|
||||
+
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{listMember}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function MemberDisplay({
|
||||
member,
|
||||
isCoach,
|
||||
idTeam,
|
||||
currentUserId,
|
||||
}: {
|
||||
member: Member
|
||||
isCoach: boolean
|
||||
idTeam: number
|
||||
currentUserId: number
|
||||
}) {
|
||||
return (
|
||||
<div className="member">
|
||||
<img
|
||||
id="profile-picture"
|
||||
src={member.user.profilePicture}
|
||||
alt="Photo de profile"
|
||||
/>
|
||||
<p>{member.user.name}</p>
|
||||
<p>{member.role}</p>
|
||||
<p>{member.user.email}</p>
|
||||
{isCoach && currentUserId !== member.user.id && (
|
||||
<button
|
||||
id="delete"
|
||||
onClick={() =>
|
||||
confirm(
|
||||
"Êtes-vous sûr de retirer ce membre de l'équipe?",
|
||||
)
|
||||
? (window.location.href = `${BASE}/team/${idTeam}/remove/${member.user.id}`)
|
||||
: {}
|
||||
}>
|
||||
Retirer
|
||||
</button>
|
||||
)}
|
||||
{isCoach && currentUserId == member.user.id && (
|
||||
<button
|
||||
id="delete"
|
||||
onClick={() =>
|
||||
confirm("Êtes-vous sûr de quitter cette équipe?")
|
||||
? (window.location.href = `${BASE}/team/${idTeam}/remove/${member.user.id}`)
|
||||
: {}
|
||||
}>
|
||||
Quitter
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Insertion view</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #f1f1f1;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 400px;
|
||||
margin: 5px auto;
|
||||
padding: 20px;
|
||||
background-color: #fff;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
h2 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
{% for item in bad_fields %}
|
||||
#{{ item }}{
|
||||
border-color: red;
|
||||
}{% endfor %} input[type="text"], input[type="password"] {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
input[type="submit"] {
|
||||
background-color: #007bff;
|
||||
color: #fff;
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input[type="submit"]:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="container">
|
||||
<h2>Modifier votre équipe</h2>
|
||||
<form action="{{ path('/team/' ~ team.getInfo().getId() ~ '/edit') }}" method="post">
|
||||
<div class="form-group">
|
||||
<label for="name">Nom de l'équipe :</label>
|
||||
<input type="text" id="name" name="name" value="{{ team.getInfo().getName() }}" required>
|
||||
<label for="picture">Logo:</label>
|
||||
<input type="text" id="picture" name="picture" value="{{ team.getInfo().getPicture() }}" required>
|
||||
<label for="main_color">Couleur principale</label>
|
||||
<input type="color" value="{{ team.getInfo().getMainColor() }}" id="main_color" name="main_color" required>
|
||||
<label for="second_color">Couleur secondaire</label>
|
||||
<input type="color" id="second_color" name="second_color" value="{{ team.getInfo().getSecondColor() }}" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="submit" value="Confirmer">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -1,44 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace IQBall\Core\Data;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
class Color {
|
||||
/**
|
||||
* @var string that represents an hexadecimal color code
|
||||
*/
|
||||
private string $hex;
|
||||
|
||||
/**
|
||||
* @param string $value 6 bytes unsigned int that represents an RGB color
|
||||
* @throws InvalidArgumentException if the value is negative or greater than 0xFFFFFF
|
||||
*/
|
||||
|
||||
private function __construct(string $value) {
|
||||
$this->hex = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getValue(): string {
|
||||
return $this->hex;
|
||||
}
|
||||
|
||||
public static function from(string $value): Color {
|
||||
$color = self::tryFrom($value);
|
||||
if ($color == null) {
|
||||
var_dump($value);
|
||||
throw new InvalidArgumentException("The string is not an hexadecimal code");
|
||||
}
|
||||
return $color;
|
||||
}
|
||||
|
||||
public static function tryFrom(string $value): ?Color {
|
||||
if (!preg_match('/#(?:[0-9a-fA-F]{6})/', $value)) {
|
||||
return null;
|
||||
}
|
||||
return new Color($value);
|
||||
}
|
||||
}
|
@ -1,68 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace IQBall\Core\Data;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Enumeration class workaround
|
||||
* As there is no enumerations in php 7.4, this class
|
||||
* encapsulates an integer value and use it as a variant discriminant
|
||||
*/
|
||||
final class MemberRole {
|
||||
private const ROLE_PLAYER = 0;
|
||||
private const ROLE_COACH = 1;
|
||||
private const MIN = self::ROLE_PLAYER;
|
||||
private const MAX = self::ROLE_COACH;
|
||||
|
||||
private int $value;
|
||||
|
||||
private function __construct(int $val) {
|
||||
if (!$this->isValid($val)) {
|
||||
throw new InvalidArgumentException("Valeur du rôle invalide");
|
||||
}
|
||||
$this->value = $val;
|
||||
}
|
||||
|
||||
public static function player(): MemberRole {
|
||||
return new MemberRole(MemberRole::ROLE_PLAYER);
|
||||
}
|
||||
|
||||
public static function coach(): MemberRole {
|
||||
return new MemberRole(MemberRole::ROLE_COACH);
|
||||
}
|
||||
|
||||
public function name(): string {
|
||||
switch ($this->value) {
|
||||
case self::ROLE_COACH:
|
||||
return "COACH";
|
||||
case self::ROLE_PLAYER:
|
||||
return "PLAYER";
|
||||
}
|
||||
die("unreachable");
|
||||
}
|
||||
|
||||
public static function fromName(string $name): ?MemberRole {
|
||||
switch ($name) {
|
||||
case "COACH":
|
||||
return MemberRole::coach();
|
||||
case "PLAYER":
|
||||
return MemberRole::player();
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private function isValid(int $val): bool {
|
||||
return ($val <= self::MAX and $val >= self::MIN);
|
||||
}
|
||||
|
||||
public function isPlayer(): bool {
|
||||
return ($this->value == self::ROLE_PLAYER);
|
||||
}
|
||||
|
||||
public function isCoach(): bool {
|
||||
return ($this->value == self::ROLE_COACH);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
namespace IQBall\Core\Data;
|
||||
|
||||
use _PHPStan_4c4f22f13\Nette\Utils\Json;
|
||||
|
||||
class User implements \JsonSerializable {
|
||||
/**
|
||||
* @var string $email user's mail address
|
||||
*/
|
||||
private string $email;
|
||||
|
||||
/**
|
||||
* @var string the user's username
|
||||
*/
|
||||
private string $name;
|
||||
|
||||
/**
|
||||
* @var int the user's id
|
||||
*/
|
||||
private int $id;
|
||||
|
||||
/**
|
||||
* @var string user's profile picture
|
||||
*/
|
||||
private string $profilePicture;
|
||||
|
||||
/**
|
||||
* @param string $email
|
||||
* @param string $name
|
||||
* @param int $id
|
||||
* @param string $profilePicture
|
||||
*/
|
||||
public function __construct(string $email, string $name, int $id, string $profilePicture) {
|
||||
$this->email = $email;
|
||||
$this->name = $name;
|
||||
$this->id = $id;
|
||||
$this->profilePicture = $profilePicture;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getEmail(): string {
|
||||
return $this->email;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getId(): int {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getProfilePicture(): string {
|
||||
return $this->profilePicture;
|
||||
}
|
||||
|
||||
public function jsonSerialize() {
|
||||
return get_object_vars($this);
|
||||
}
|
||||
}
|
Loading…
Reference in new issue