diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..0aaa092 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,38 @@ +{ + // Utilisez IntelliSense pour en savoir plus sur les attributs possibles. + // Pointez pour afficher la description des attributs existants. + // Pour plus d'informations, visitez : https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Launch built-in server and debug", + "type": "php", + "request": "launch", + "runtimeArgs": [ + "-S", + "localhost:8080", + "-t", + "./public" + ], + "port": 8080, + "serverReadyAction": { + "action": "openExternally" + } + }, + { + "name": "Debug current script in console", + "type": "php", + "request": "launch", + "program": "${file}", + "cwd": "${fileDirname}", + "externalConsole": false, + "port": 9003 + }, + { + "name": "Listen for Xdebug", + "type": "php", + "request": "launch", + "port": 9003 + } + ] +} \ No newline at end of file diff --git a/config.php b/config.php index 6e510c8..01ae8c4 100644 --- a/config.php +++ b/config.php @@ -10,7 +10,6 @@ const SUPPORTS_FAST_REFRESH = _SUPPORTS_FAST_REFRESH; /** * Maps the given relative source uri (relative to the `/front` folder) to its actual location depending on imported profile. * @param string $assetURI relative uri path from `/front` folder - * @return string valid url that points to the given uri */ function asset(string $assetURI): string { return _asset($assetURI); diff --git a/front/assets/account.svg b/front/assets/account.svg new file mode 100644 index 0000000..70d7391 --- /dev/null +++ b/front/assets/account.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/front/assets/court/court.svg b/front/assets/court/court.svg new file mode 100644 index 0000000..e01fd58 --- /dev/null +++ b/front/assets/court/court.svg @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/front/components/editor/BasketCourt.tsx b/front/components/editor/BasketCourt.tsx index e57d669..e59bdb7 100644 --- a/front/components/editor/BasketCourt.tsx +++ b/front/components/editor/BasketCourt.tsx @@ -8,6 +8,7 @@ import { useState, } from "react" import CourtPlayer from "./CourtPlayer" + import { Player } from "../../tactic/Player" import { Action, ActionKind } from "../../tactic/Action" import ArrowAction from "../actions/ArrowAction" diff --git a/front/style/ball.css b/front/style/ball.css index c14c196..5169ec7 100644 --- a/front/style/ball.css +++ b/front/style/ball.css @@ -9,3 +9,6 @@ height: 20px; cursor: pointer; } + +.ball-div:focus-within { +} diff --git a/front/style/home.css b/front/style/home.css new file mode 100644 index 0000000..780bb84 --- /dev/null +++ b/front/style/home.css @@ -0,0 +1,114 @@ +@import url(variable.css); + +body { + /* background-color: #303030; */ +} +#main { + /* margin-left : 10%; + margin-right: 10%; */ + /* border : solid 1px #303030; */ + display: flex; + flex-direction: column; + font-family: Helvetica; + height: 100%; +} + +#header { + text-align: center; + background-color: var(--main-color); + margin: 0px; + /* border : var(--accent-color) 1px ésolid; */ + display: flex; + flex-direction: row; + /* border-radius: 0.75cap; */ +} + +#IQBall { + color: var(--accent-color); + font-weight: bold; + font-size: 45px; +} + +#IQBall { + color: #ffa238; + font-weight: bold; + font-size: 45px; +} +#body { + display: flex; + flex-direction: row; + margin: 0px; + height: 100%; + background-color: var(--second-color); +} + +.data { + border: 1.5px solid var(--main-contrast-color); + background-color: var(--main-color); + border-radius: 0.75cap; + color: var(--main-contrast-color); +} + +.data:hover { + border-color: var(--accent-color); + cursor: pointer; +} + +.listTactic { + display: block; +} + +.SetButton { + width: 80%; + margin-left: 5%; + margin-top: 5%; +} + +#img-account { + width: 100%; + cursor: pointer; +} + +#header-right, +#header-left { + width: 10%; + /* border: yellow 2px solid; */ +} + +#header-right { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +#username { + color: var(--main-contrast-color); + margin: 0; +} + +#clickable-header-right:hover #username { + color: var(--accent-color); +} + +#header-center { + width: 80%; +} + +#clickable-header-right { + width: 40%; + border-radius: 1cap; + padding: 2%; +} + +#clickable-header-right:hover { + border: orange 1px solid; +} + +.clickable { + cursor: pointer; +} + +#img-account { + width: 100%; +} diff --git a/front/style/home/titre.css b/front/style/home/titre.css new file mode 100644 index 0000000..ccc7326 --- /dev/null +++ b/front/style/home/titre.css @@ -0,0 +1,94 @@ + +#main { + margin-left : 10%; + margin-right: 10%; + border : solid 2px purple; + display: flex; + flex-direction: column; + font-family: Helvetica,; +} + +.new { + border-radius: 100%; +} + +#header { + text-align: center; +<<<<<<< HEAD:front/style/home.css + background-color: green; + margin : 0px; +} + +#body { + display: flex; + flex-direction: row; + border : solid 10px violet; + margin:0px + } + +#personal-space { + background-color: orange; + display: flex; + flex-direction: column; + +} + +#sideMenu { + background-color: grey; +} + +#titlePersonalSpace h2 { + text-align: center; +} + +#sideMenu h2 { + display: inline-block; + margin-right : 5%; +} + +.titreSideMenu { + border-bottom: black solid 2px; + width: 95%; + +} + +#sideMenu .title { + font-size: 13px; + font-weight: bold; + color : #FFFFFF; + letter-spacing: 1px; + text-transform: uppercase; + background-color: black; + padding : 1.5%; + margin-bottom: 0px; +} + +#bodyPersonalSpace { + width: 95%; + border : 1px red solid; + align-self: center; +} +#bodyPersonalSpace table{ + width: 100%; + border-collapse : separate; + border-spacing : 1em; +} + +td { + border : 3px solid black; + padding-bottom : 1%; + padding-top : 1%; + margin: 80px; + text-align: center; +} + +td:hover { + background-color: red; + +======= +} + +#title { + background-color: aqua; +>>>>>>> 6d36115 (WIP page home):front/style/home/titre.css +} \ No newline at end of file diff --git a/front/style/personnal_space.css b/front/style/personnal_space.css new file mode 100644 index 0000000..4f0c67d --- /dev/null +++ b/front/style/personnal_space.css @@ -0,0 +1,40 @@ +#personal-space { + display: flex; + flex-direction: column; +} + +#titlePersonalSpace h2 { + text-align: center; + color: var(--main-contrast-color); + /* font-family: Helvetica; + font-weight: bold; */ +} + +#bodyPersonalSpace { + width: 95%; + /* background-color: #ccc2b7; */ + border: 3px var(--main-color) solid; + border-radius: 0.5cap; + align-self: center; +} + +#bodyPersonalSpace table { + width: 100%; + border-collapse: separate; + border-spacing: 1em; + table-layout: fixed; + overflow: hidden; +} + +#bodyPersonalSpace td { + width: 80px !important; + padding-bottom: 1%; + padding-top: 1%; + height: fit-content; + text-align: center; + overflow: hidden; +} + +tbody p { + text-align: center; +} diff --git a/front/style/side_menu.css b/front/style/side_menu.css new file mode 100644 index 0000000..0344971 --- /dev/null +++ b/front/style/side_menu.css @@ -0,0 +1,53 @@ +@import url(variable.css); + +#sideMenu { + background-color: var(--third-color); + display: flex; + flex-direction: column; + align-items: center; + overflow: hidden; +} + +#sideMenu h2 { + display: inline-block; + margin-right: 5%; +} + +#sideMenuContent { + width: 90%; +} +.titreSideMenu { + border-bottom: var(--main-color) solid 3px; + width: 100%; + margin-bottom: 3%; +} + +#sideMenu .title { + font-size: 12px; + font-weight: bold; + color: var(--main-contrast-color); + letter-spacing: 1px; + text-transform: uppercase; + background-color: var(--main-color); + padding: 3%; + margin-bottom: 0px; + margin-right: 3%; +} + +.new { + border-radius: 100%; +} + +.buttonSideMenu { + /* border : black solid 1px; */ + border-radius: 0.5cap; + width: fit-content; + padding: 2%; + margin-top: 3%; + overflow: hidden; +} + +.buttonSideMenu:hover { + /* background-color: #c9d1e0; */ + cursor: pointer; +} diff --git a/front/style/variable.css b/front/style/variable.css new file mode 100644 index 0000000..e50ec93 --- /dev/null +++ b/front/style/variable.css @@ -0,0 +1,7 @@ +:root { + --main-color: #191a21; + --second-color: #282a36; + --third-color: #303341; + --accent-color: #ffa238; + --main-contrast-color: #e6edf3; +} diff --git a/front/views/Home.tsx b/front/views/Home.tsx new file mode 100644 index 0000000..d44571d --- /dev/null +++ b/front/views/Home.tsx @@ -0,0 +1,306 @@ +import "../style/home.css" +import "../style/personnal_space.css" +import "../style/side_menu.css" +// import AccountSvg from "../assets/account.svg?react" +import { CSSProperties, useRef } from "react" + +interface Tactic { + id: number + name: string + creation_date: string +} + +interface Team { + id: number + name: string + picture: string + main_color: string + second_color: string +} + +export default function Home({ + lastTactics, + allTactics, + teams, + username, +}: { + lastTactics: Tactic[] + allTactics: Tactic[] + teams: Team[] + username: string +}) { + return ( +
+ + <Body + lastTactics={lastTactics} + allTactics={allTactics} + teams={teams} + /> + </div> + ) +} + +/** + * + * @param param0 username + * @returns Header + */ +export function Title({ username }: { username: string }) { + return ( + <div id="header"> + <div id="header-left"></div> + <div id="header-center"> + <h1 + id="IQBall" + className="clickable" + onClick={() => { + location.pathname = "/" + }}> + <span id="IQ">IQ</span> + <span id="Ball">Ball</span> + </h1> + </div> + <div id="header-right"> + <div className="clickable" id="clickable-header-right"> + {/* <AccountSvg id="img-account" /> */} + <img + id="img-account" + src="account.svg" + onClick={() => { + location.pathname = "/settings" + }} + /> + <p id="username">{username}</p> + </div> + </div> + </div> + ) +} + +export function Body({ + lastTactics, + allTactics, + teams, +}: { + lastTactics: Tactic[] + allTactics: Tactic[] + teams: Team[] +}) { + const widthPersonalSpace = 78 + const widthSideMenu = 100 - widthPersonalSpace + return ( + <div id="body"> + <PersonalSpace width={widthPersonalSpace} allTactics={allTactics} /> + <SideMenu + width={widthSideMenu} + lastTactics={lastTactics} + teams={teams} + /> + </div> + ) +} + +export function SideMenu({ + width, + lastTactics, + teams, +}: { + width: number + lastTactics: Tactic[] + teams: Team[] +}) { + return ( + <div + id="sideMenu" + style={{ + width: width + "%", + }}> + <div id="sideMenuContent"> + <Team teams={teams} /> + <Tactic lastTactics={lastTactics} /> + </div> + </div> + ) +} + +export function PersonalSpace({ + width, + allTactics, +}: { + width: number + allTactics: Tactic[] +}) { + return ( + <div + id="personal-space" + style={{ + width: width + "%", + }}> + <TitlePersonalSpace /> + <BodyPersonalSpace allTactics={allTactics} /> + </div> + ) +} + +function TitlePersonalSpace() { + return ( + <div id="titlePersonalSpace"> + <h2>Espace Personnel</h2> + </div> + ) +} + +function TableData({ allTactics }: { allTactics: Tactic[] }) { + const nbRow = Math.floor(allTactics.length / 3) + 1 + let listTactic = Array(nbRow) + for (let i = 0; i < nbRow; i++) { + listTactic[i] = Array(0) + } + let i = 0 + let j = 0 + allTactics.forEach((tactic) => { + listTactic[i].push(tactic) + j++ + if (j === 3) { + i++ + j = 0 + } + }) + + i = 0 + while (i < nbRow) { + listTactic[i] = listTactic[i].map((tactic: Tactic) => ( + <td + key={tactic.id} + className="data" + onClick={() => { + location.pathname = "/tactic/" + tactic.id + "/edit" + }}> + {troncName(tactic.name, 25)} + </td> + )) + i++ + } + if (nbRow == 1) { + if (listTactic[0].length < 3) { + for (let i = 0; i <= 3 - listTactic[0].length; i++) { + listTactic[0].push(<td key={"tdNone" + i}></td>) + } + } + } + + const data = listTactic.map((tactic, rowIndex) => ( + <tr key={rowIndex + "row"}>{tactic}</tr> + )) + return data +} + +function BodyPersonalSpace({ allTactics }: { allTactics: Tactic[] }) { + let data + if (allTactics.length == 0) { + data = <p>Aucune tactique créé !</p> + } else { + data = <TableData allTactics={allTactics} /> + } + + return ( + <div id="bodyPersonalSpace"> + <table> + <tbody key="tbody">{data}</tbody> + </table> + </div> + ) +} + +export function Team({ teams }: { teams: Team[] }) { + const listTeam = teams.map((team, rowIndex) => ( + <li key={"team" + rowIndex}> + {team.name} + <button onClick={() => (location.pathname = "/team/" + team.id)}> + open + </button> + </li> + )) + return ( + <div id="teams"> + <div className="titreSideMenu"> + <h2 className="title">Mes équipes</h2> + <button + className="new" + onClick={() => (location.pathname = "/team/new")}> + + + </button> + </div> + <SetButtonTeam teams={teams} /> + </div> + ) +} + +export function Tactic({ lastTactics }: { lastTactics: Tactic[] }) { + return ( + <div id="tactic"> + <div className="titreSideMenu"> + <h2 className="title">Mes dernières stratégies</h2> + <button + className="new" + id="createTactic" + onClick={() => (location.pathname = "/tactic/new")}> + + + </button> + </div> + <SetButtonTactic tactics={lastTactics} /> + </div> + ) +} + +function SetButtonTactic({ tactics }: { tactics: Tactic[] }) { + const lastTactics = tactics.map((tactic) => ( + <ButtonLastTactic tactic={tactic} /> + )) + return <div className="SetButton">{lastTactics}</div> +} + +function SetButtonTeam({ teams }: { teams: Team[] }) { + const listTeam = teams.map((teams) => <ButtonTeam team={teams} />) + return <div className="SetButton">{listTeam}</div> +} + +function ButtonTeam({ team }: { team: Team }) { + const name = troncName(team.name, 20) + return ( + <div> + <div + id={"ButtonTeam" + team.id} + className="buttonSideMenu data" + onClick={() => { + location.pathname = "/team/" + team.id + }}> + {name} + </div> + </div> + ) +} + +function ButtonLastTactic({ tactic }: { tactic: Tactic }) { + const name = troncName(tactic.name, 20) + return ( + <div + id={"Button" + tactic.id} + className="buttonSideMenu data" + onClick={() => { + location.pathname = "/tactic/" + tactic.id + "/edit" + }}> + {name} + </div> + ) +} + +function troncName(name: string, limit: number): string { + if (name.length > limit) { + name = name.substring(0, limit) + "..." + } else { + name = name + } + return name +} diff --git a/front/views/ProfilPage.tsx b/front/views/ProfilPage.tsx new file mode 100644 index 0000000..e69de29 diff --git a/public/account.svg b/public/account.svg new file mode 100644 index 0000000..70d7391 --- /dev/null +++ b/public/account.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M234-276q51-39 114-61.5T480-360q69 0 132 22.5T726-276q35-41 54.5-93T800-480q0-133-93.5-226.5T480-800q-133 0-226.5 93.5T160-480q0 59 19.5 111t54.5 93Zm246-164q-59 0-99.5-40.5T340-580q0-59 40.5-99.5T480-720q59 0 99.5 40.5T620-580q0 59-40.5 99.5T480-440Zm0 360q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q53 0 100-15.5t86-44.5q-39-29-86-44.5T480-280q-53 0-100 15.5T294-220q39 29 86 44.5T480-160Zm0-360q26 0 43-17t17-43q0-26-17-43t-43-17q-26 0-43 17t-17 43q0 26 17 43t43 17Zm0-60Zm0 360Z" fill="#e6edf3"/></svg> \ No newline at end of file diff --git a/public/index.php b/public/index.php index 8c9a62b..935e2f2 100644 --- a/public/index.php +++ b/public/index.php @@ -1,6 +1,5 @@ <?php - require "../vendor/autoload.php"; require "../config.php"; require "../sql/database.php"; @@ -39,7 +38,7 @@ function getConnection(): Connection { } function getUserController(): UserController { - return new UserController(new TacticModel(new TacticInfoGateway(getConnection()))); + return new UserController(new TacticModel(new TacticInfoGateway(getConnection())), new TeamModel(new TeamGateway(getConnection()), new MemberGateway(getConnection()), new AccountGateway(getConnection()))); } function getVisualizerController(): VisualizerController { @@ -122,7 +121,6 @@ function runMatch($match, MutableSessionHandle $session): HttpResponse { return App::runAction($basePath . '/login', $match['target'], $match['params'], $session); } - //this is a global variable $basePath = get_public_path(__DIR__); diff --git a/src/App/Controller/UserController.php b/src/App/Controller/UserController.php index 5ce1318..a995879 100644 --- a/src/App/Controller/UserController.php +++ b/src/App/Controller/UserController.php @@ -7,15 +7,19 @@ use IQBall\App\Session\SessionHandle; use IQBall\App\ViewHttpResponse; use IQBall\Core\Http\HttpResponse; use IQBall\Core\Model\TacticModel; +use IQBall\Core\Model\TeamModel; class UserController { private TacticModel $tactics; + private ?TeamModel $teams; + /** * @param TacticModel $tactics */ - public function __construct(TacticModel $tactics) { + public function __construct(TacticModel $tactics, ?TeamModel $teams = null) { $this->tactics = $tactics; + $this->teams = $teams; } /** @@ -23,9 +27,29 @@ class UserController { * @return ViewHttpResponse the home page view */ public function home(SessionHandle $session): ViewHttpResponse { - //TODO use session's account to get the last 5 tactics of the logged-in account - $listTactic = $this->tactics->getLast(5); - return ViewHttpResponse::twig("home.twig", ["recentTactic" => $listTactic]); + $limitNbTactics = 5; + $lastTactics = $this->tactics->getLast($limitNbTactics, $session->getAccount()->getId()); + $allTactics = $this->tactics->getAll($session->getAccount()->getId()); + $name = $session->getAccount()->getName(); + + //TODO + if ($this->teams != null) { + $teams = $this->teams->getAll($session->getAccount()->getId()); + } else { + $teams = []; + } + + return ViewHttpResponse::react("views/Home.tsx", [ + "lastTactics" => $lastTactics, + "allTactics" => $allTactics, + "teams" => $teams, + "username" => $name, + ]); + // return ViewHttpResponse::react("views/Home.tsx", []); + } + + public function homeTwig(SessionHandle $session): ViewHttpResponse { + return ViewHttpResponse::twig("home.twig", []); } /** diff --git a/src/Core/Gateway/TacticInfoGateway.php b/src/Core/Gateway/TacticInfoGateway.php index 67cffc4..08302c9 100644 --- a/src/Core/Gateway/TacticInfoGateway.php +++ b/src/Core/Gateway/TacticInfoGateway.php @@ -45,13 +45,40 @@ class TacticInfoGateway { * @param integer $nb * @return array<array<string,mixed>> */ - public function getLast(int $nb): ?array { + public function getLast(int $nb, int $ownerId): ?array { $res = $this->con->fetch( - "SELECT * FROM Tactic ORDER BY creation_date DESC LIMIT :nb ", - [":nb" => [$nb, PDO::PARAM_INT]] + "SELECT * + FROM Tactic + WHERE owner = :ownerId + ORDER BY creation_date DESC + LIMIT :nb", + [ + ":ownerId" => [$ownerId, PDO::PARAM_INT],":nb" => [$nb, PDO::PARAM_INT], + ] ); if (count($res) == 0) { - return null; + return []; + } + return $res; + } + + /** + * Get all the tactics of the owner + * + * @return array<array<string,mixed>> + */ + public function getAll(int $ownerId): ?array { + $res = $this->con->fetch( + "SELECT * + FROM Tactic + WHERE owner = :ownerId + ORDER BY name DESC", + [ + ":ownerId" => [$ownerId, PDO::PARAM_INT], + ] + ); + if (count($res) == 0) { + return []; } return $res; } diff --git a/src/Core/Gateway/TeamGateway.php b/src/Core/Gateway/TeamGateway.php index d775eda..c5cc115 100644 --- a/src/Core/Gateway/TeamGateway.php +++ b/src/Core/Gateway/TeamGateway.php @@ -81,5 +81,14 @@ class TeamGateway { )[0]['id'] ?? null; } - + /** + * Undocumented function + * + * @param integer $user + * @return array<array<string, mixed>> + */ + public function getAll(int $user): array { + return $this->con->fetch("SELECT * FROM Team", []); + } + } diff --git a/src/Core/Model/TacticModel.php b/src/Core/Model/TacticModel.php index 51e5eb8..7057e7f 100644 --- a/src/Core/Model/TacticModel.php +++ b/src/Core/Model/TacticModel.php @@ -3,6 +3,7 @@ namespace IQBall\Core\Model; use IQBall\Core\Data\CourtType; +use IQBall\App\Session\SessionHandle; use IQBall\Core\Data\TacticInfo; use IQBall\Core\Gateway\TacticInfoGateway; use IQBall\Core\Validation\ValidationFail; @@ -57,10 +58,27 @@ class TacticModel { * @param integer $nb * @return array<array<string,mixed>> */ - public function getLast(int $nb): ?array { - return $this->tactics->getLast($nb); + + /** + * Return the nb last tactics + * + * @param integer $nb + * @param integer $ownerId + * @return array<array<string,mixed>> + */ + public function getLast(int $nb, int $ownerId): array { + return $this->tactics->getLast($nb, $ownerId); } + /** + * Get all the tactics of the owner + * + * @param integer $ownerId + * @return array<array<string,mixed>> + */ + public function getAll(int $ownerId): ?array { + return $this->tactics->getAll($ownerId); + } /** * Update the name of a tactic * @param int $id the tactic identifier diff --git a/src/Core/Model/TeamModel.php b/src/Core/Model/TeamModel.php index f6af837..3d5ade4 100644 --- a/src/Core/Model/TeamModel.php +++ b/src/Core/Model/TeamModel.php @@ -79,4 +79,14 @@ class TeamModel { return $teamId; } + /** + * Get all user's teams + * + * @param integer $user + * @return array<array<string, mixed>> + */ + public function getAll(int $user) : array { + return $this->teams->getAll($user); + } + }