diff --git a/front/assets/icon/share.png b/front/assets/icon/share.png new file mode 100644 index 0000000..b6347d3 Binary files /dev/null and b/front/assets/icon/share.png differ diff --git a/front/style/home/home.css b/front/style/home/home.css index 455e3af..a08a199 100644 --- a/front/style/home/home.css +++ b/front/style/home/home.css @@ -22,6 +22,7 @@ body { margin: 0px; height: 100%; background-color: var(--second-color); + overflow: hidden; } .data { @@ -29,6 +30,7 @@ body { background-color: var(--main-color); border-radius: 0.75cap; color: var(--main-contrast-color); + position: relative; } .data:hover { diff --git a/front/style/home/personnal_space.css b/front/style/home/personnal_space.css index ba5a9a7..8875868 100644 --- a/front/style/home/personnal_space.css +++ b/front/style/home/personnal_space.css @@ -31,8 +31,31 @@ padding-top: 1%; height: fit-content; text-align: center; + overflow: hidden; } tbody p { text-align: center; } + +.share-icon-container { + position: absolute; + top: 50%; + right: 0; + transform: translateY(-50%); + padding: 5px; +} + +.share-button { + cursor: pointer; +} + +.share-icon-button { + background-color: transparent; + border: none; +} + +.share-icon { + color: white; + font-size: 2em; +} diff --git a/front/style/home/side_menu.css b/front/style/home/side_menu.css index 3a23947..f2371dd 100644 --- a/front/style/home/side_menu.css +++ b/front/style/home/side_menu.css @@ -16,6 +16,7 @@ #side-menu-content { width: 90%; } + .titre-side-menu { border-bottom: var(--main-color) solid 3px; width: 100%; diff --git a/front/style/team_panel.css b/front/style/team_panel.css index 151b2da..90597ab 100644 --- a/front/style/team_panel.css +++ b/front/style/team_panel.css @@ -30,6 +30,7 @@ body { align-items: center; width: 68%; margin-right: 2%; + margin-bottom: 15px; } #right-panel { @@ -37,6 +38,7 @@ body { flex-direction: column; align-items: center; width: 30%; + flex-grow: 1; } header { @@ -55,6 +57,10 @@ header h1 a { font-size: 1.4em; } +html, body, #main-div, #content-container, #right-panel, #tactics { + height: 100%; +} + .square { width: 50px; height: 50px; @@ -181,6 +187,8 @@ header h1 a { width: 100%; height: 100%; border-radius: 10px; + overflow: auto; + margin-bottom: 5px; } #head-tactics { diff --git a/front/views/Home.tsx b/front/views/Home.tsx index 35c59a3..00091f5 100644 --- a/front/views/Home.tsx +++ b/front/views/Home.tsx @@ -5,9 +5,10 @@ import { BASE } from "../Constants" import Draggable from "react-draggable"; import {NULL_POS} from "../components/arrows/Pos"; import {contains} from "../components/arrows/Box"; -import {useRef, useState} from "react"; +import React, {useRef, useState} from "react"; import { fetchAPI } from "../Fetcher" import {User} from "../model/User"; +import { FaShare } from "react-icons/fa"; import {SaveStates} from "../components/editor/SavingState"; interface Tactic { @@ -83,7 +84,7 @@ function SideMenu({ teams: Team[] }) { return ( -
@@ -153,7 +154,7 @@ function TableData({ i = 0 while (i < nbRow) { listTactic[i] = listTactic[i].map((tactic: Tactic, i) => ( - + )) i++ } @@ -175,23 +176,35 @@ function TableData({ function DraggableTableDataElement({ tactic, teams, - user, } : { tactic : Tactic teams : Team[] - user : User }) { const ref = useRef(null) const [dragging, setDragging] = useState(false) + const [hovered, setHovered] = useState(false); + + const handleButtonClick = (event: React.MouseEvent) => { + event.stopPropagation(); + if (!dragging) { + const userEmail = window.prompt("Entrez l'email à qui partager la tactique :"); + if(userEmail != null) { + onShareTactic(userEmail, tactic); + } + } else { + setDragging(false); + } + }; + return ( setDragging(true)} onStop={() => { if(dragging) { - onDropTactic(ref.current.getBoundingClientRect(), tactic, teams, user) + onDropTactic(ref.current.getBoundingClientRect(), tactic, teams) } }} - > + > + onMouseEnter={() => setHovered(true)} + onMouseLeave={() => setHovered(false)} + > + {truncateString(tactic.name, 25)} + {hovered && ( +
+ +
+ )}
) @@ -316,22 +339,35 @@ function truncateString(name: string, limit: number): string { return name } -function onDropTactic(ref : DOMRect, tactic : Tactic, teams : Team[], user : User) { +function onDropTactic(ref : DOMRect, tactic : Tactic, teams : Team[]) { let shared = false; for (const team of teams) { if (contains(ref, document.getElementById(`button-team-${team.id}`)!.getBoundingClientRect())) { if (!shared) { - shareTacticToTeam(tactic, team, user); + shareTacticToTeam(tactic, team); shared = true; } } } } -async function shareTacticToTeam(tactic : Tactic, team : Team, user : User) { - const canShare = await fetchAPI(`tactic/${tactic.id}/can-share-to-team`, team).then((r) => r.ok); +async function onShareTactic(email: string, tactic: Tactic) { + const canShareResponse = await fetchAPI(`tactic/${tactic.id}/can-share`, tactic); + if (canShareResponse.ok) { + const shareToAccountResponse = await fetchAPI(`tactic/${tactic.id}/share-to-account`, email); + if (!shareToAccountResponse.ok) { + alert("Une erreur s'est produite lors du partage de la tactique avec ce compte"); + } + } else { + alert("Vous ne pouvez pas partager cette tactique"); + } +} + + +async function shareTacticToTeam(tactic : Tactic, team : Team) { + const canShare = await fetchAPI(`tactic/${tactic.id}/can-share-to-team`, team).then((r) => r.ok) if(canShare && confirm("Etes-vous sûr de vouloir partager la tactique " + tactic.name + " avec l'équipe " + team.name)) { - fetchAPI(`tactic/${tactic.id}/share`, team) + fetchAPI(`tactic/${tactic.id}/share-to-team`, team) } if(!canShare) { alert("Vous ne pouvez pas partager cette tactique à cette équipe") diff --git a/front/views/TeamPanel.tsx b/front/views/TeamPanel.tsx index 4856d55..8ba5aa5 100644 --- a/front/views/TeamPanel.tsx +++ b/front/views/TeamPanel.tsx @@ -19,7 +19,7 @@ export default function TeamPanel({ const [teamTactics, setTeamTactics] = useState(tactics); function handleTacticDelete(tacticId) { - fetchAPI(`tactic/${tacticId}/unshare`, team.info) + fetchAPI(`tactic/${tacticId}/unshare-to-team`, team.info) const updatedTactics = teamTactics.filter((tactic) => tactic.id !== tacticId); setTeamTactics(updatedTactics); } @@ -114,6 +114,7 @@ function MembersDisplay({ }) { const listMember = members.map((member) => ( void }) { return ( -
+
location.pathname = BASE + "/tactic/" + tactic.id + "/edit"}>

{tactic.name}

{isCoach && ( diff --git a/package.json b/package.json index 1380d59..d330e30 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-draggable": "^4.4.6", + "react-icons": "^5.0.1", "typescript": "^5.2.2", "vite": "^4.5.0", "vite-plugin-css-injected-by-js": "^3.3.0" diff --git a/public/api/index.php b/public/api/index.php index 10a5326..5d63f68 100644 --- a/public/api/index.php +++ b/public/api/index.php @@ -39,9 +39,11 @@ function getRoutes(): AltoRouter { $router->map("POST", "/tactic/[i:id]/can-share", Action::auth(fn(int $id, Account $acc) => getTacticController()->canShareTactic($id, $acc))); $router->map("POST", "/tactic/[i:id]/can-share-to-team", Action::auth(fn(int $id, Account $acc) => getTacticController()->canShareTacticToTeam($id, $acc))); - $router->map("POST", "/tactic/[i:id]/share", Action::auth(fn(int $id, Account $acc) => getTacticController()->shareTacticToTeam($id, $acc))); + $router->map("POST", "/tactic/[i:id]/share-to-team", Action::auth(fn(int $id, Account $acc) => getTacticController()->shareTacticToTeam($id, $acc))); + $router->map("POST", "/tactic/[i:id]/share-to-account", Action::auth(fn(int $id, Account $acc) => getTacticController()->shareTacticToAccount($id, $acc))); - $router->map("POST", "/tactic/[i:id]/unshare", Action::auth(fn(int $id, Account $acc) => getTacticController()->unshareTactic($id, $acc))); + + $router->map("POST", "/tactic/[i:id]/unshare-to-team", Action::auth(fn(int $id, Account $acc) => getTacticController()->unshareTacticToTeam($id, $acc))); return $router; } diff --git a/src/Api/Controller/APITacticController.php b/src/Api/Controller/APITacticController.php index 50c291e..234e4d7 100644 --- a/src/Api/Controller/APITacticController.php +++ b/src/Api/Controller/APITacticController.php @@ -68,14 +68,15 @@ class APITacticController { } - public function canShareTactic(int $tacticId, Account $account) : bool { + public function canShareTactic(int $tacticId, Account $account): HttpResponse { if ($this->tacticModel->canShareTactic($tacticId, $account)) { - return true; + return HttpResponse::fromCode(HttpCodes::OK); } - return false; + return new JsonHttpResponse(["message" => "Vous ne pouvez pas partager cette tactique"], HttpCodes::FORBIDDEN); } - public function canShareTacticToTeam(int $tacticId, Account $account) { + + public function canShareTacticToTeam(int $tacticId, Account $account): HttpResponse { return Control::runChecked([ "id" => [], "name" => [], @@ -83,17 +84,17 @@ class APITacticController { "main_color" => [], "second_color" => [] ], function (HttpRequest $request) use ($tacticId, $account) { - if($this->canShareTactic($tacticId, $account)) { - if($this->teamModel->canShareTacticToTeam($request["id"], $account->getUser()->getEmail())) { + if ($this->canShareTactic($tacticId, $account)) { + if ($this->teamModel->canShareTacticToTeam($request["id"], $account->getUser()->getEmail())) { return HttpResponse::fromCode(HttpCodes::OK); } } - return HttpResponse::fromCode(HttpCodes::BAD_REQUEST); - } - ); + return new JsonHttpResponse(["message" => "Action non autorisée"], HttpCodes::FORBIDDEN); + }); } - public function shareTacticToTeam(int $tacticId, Account $account) { + + public function shareTacticToTeam(int $tacticId, Account $account): HttpResponse { return Control::runChecked([ "id" => [], "name" => [], @@ -103,11 +104,19 @@ class APITacticController { ], function (HttpRequest $request) use ($tacticId, $account) { $this->teamModel->shareTacticToTeam($request["id"], $tacticId); return HttpResponse::fromCode(HttpCodes::OK); - } - ); + }); } - public function unshareTactic(int $tacticId, Account $account) { + public function shareTacticToAccount(int $tacticId, Account $account): HttpResponse { + return Control::runChecked([ + "email" => [], + ], function (HttpRequest $request) use ($tacticId, $account) { + $this->tacticModel->shareTacticToAccountMail($request["email"], $tacticId); + return HttpResponse::fromCode(HttpCodes::OK); + }); + } + + public function unshareTacticToTeam(int $tacticId, Account $account): HttpResponse { return Control::runChecked([ "id" => [], "name" => [], @@ -115,13 +124,12 @@ class APITacticController { "mainColor" => [], "secondColor" => [] ], function (HttpRequest $request) use ($tacticId, $account) { - if($this->teamModel->canShareTacticToTeam($request["id"], $account->getUser()->getEmail())) { + if ($this->teamModel->canShareTacticToTeam($request["id"], $account->getUser()->getEmail())) { $this->teamModel->unshareTacticToTeam($tacticId, $request["id"]); return HttpResponse::fromCode(HttpCodes::OK); } - return HttpResponse::fromCode(HttpCodes::BAD_REQUEST); - } - ); + return new JsonHttpResponse(["message" => "Action non autorisée"], HttpCodes::FORBIDDEN); + }); } }