Compare commits

...

4 Commits

Author SHA1 Message Date
Maël DAIM 56b6b4351c WIP resume the implementation of folders
continuous-integration/drone/push Build is failing Details
1 year ago
Maël DAIM c82b329652 WIP avancing on implementing folder into personal space
continuous-integration/drone/push Build is failing Details
1 year ago
Maël DAIM 6875c500f2 implemented tha add of a folder in personal space
continuous-integration/drone/push Build is failing Details
1 year ago
Maël DAIM e259d64387 added folder into database, added gateway/model methods
continuous-integration/drone/push Build is failing Details
1 year ago

@ -0,0 +1 @@
<svg enable-background="new 0 0 48 48" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg"><path d="m40 12h-18l-4-4h-10c-2.2 0-4 1.8-4 4v8h40v-4c0-2.2-1.8-4-4-4z" fill="#ffa000"/><path d="m40 12h-32c-2.2 0-4 1.8-4 4v20c0 2.2 1.8 4 4 4h32c2.2 0 4-1.8 4-4v-20c0-2.2-1.8-4-4-4z" fill="#ffca28"/></svg>

After

Width:  |  Height:  |  Size: 301 B

@ -0,0 +1,27 @@
import {ReactNode, useState} from "react";
import "../style/popup.css"
export interface PopupProps {
children: ReactNode[] | ReactNode,
displayState: boolean,
onClose: () => void
}
export default function Popup({children, displayState, onClose}: PopupProps) {
return (
<div id="popup-background"
style={{
display: displayState ? 'flex' : 'none',
position: "absolute",
width: "78%",
height: "79%",
overflow:"hidden"
}}
onClick={onClose}>
<div id="content" onClick={event => event.stopPropagation()}>
<button id="close-button" onClick={onClose}>X</button>
{children}
</div>
</div>
)
}

@ -38,3 +38,22 @@
tbody p {
text-align: center;
}
#new-folder-button{
padding:1px;
background: var(--accent-color);
color:white;
width: 8.5%;
}
#new-folder-form{
display:flex;
flex-direction: column;
justify-content: space-around;
}
#submit-form{
width: 40%;
align-self: end;
}

@ -0,0 +1,22 @@
@import url(theme/dark.css);
#popup-background{
background-color: rgba(0, 0, 0, 0.3);
color: white;
align-items: center;
justify-content: center;
}
#content{
padding: 5px;
border-radius: 5px;
background-color: var(--third-color);
display:flex;
flex-direction: column;
}
#close-button{
border-radius: 100px;
align-self: end;
}

@ -3,6 +3,8 @@ import "../style/home/home.css"
// import AccountSvg from "../assets/account.svg?react"
import {Header} from "./template/Header"
import {BASE} from "../Constants"
import Popup from "../components/Popup";
import {useState} from "react";
interface Tactic {
id: number
@ -18,24 +20,35 @@ interface Team {
second_color: string
}
interface Folder{
id:number
name:string
}
export default function Home({
lastTactics,
allTactics,
folders,
teams,
username,
currentFolder
}: {
lastTactics: Tactic[]
allTactics: Tactic[]
folders: Folder[]
teams: Team[]
username: string
currentFolder: number
}) {
return (
<div id="main">
<Header username={username} />
<Body
lastTactics={lastTactics}
allTactics={allTactics}
tactics={allTactics}
folders={folders}
teams={teams}
currentFolder={currentFolder}
/>
</div>
)
@ -43,18 +56,22 @@ export default function Home({
function Body({
lastTactics,
allTactics,
tactics,
folders,
teams,
currentFolder
}: {
lastTactics: Tactic[]
allTactics: Tactic[]
tactics: Tactic[]
folders: Folder[]
teams: Team[]
currentFolder: number
}) {
const widthPersonalSpace = 78
const widthSideMenu = 100 - widthPersonalSpace
return (
<div id="body">
<PersonalSpace width={widthPersonalSpace} allTactics={allTactics} />
<PersonalSpace width={widthPersonalSpace} tactics={tactics} folders={folders} currentFolder={currentFolder}/>
<SideMenu
width={widthSideMenu}
lastTactics={lastTactics}
@ -89,11 +106,16 @@ function SideMenu({
function PersonalSpace({
width,
allTactics,
tactics,
folders,
currentFolder
}: {
width: number
allTactics: Tactic[]
tactics: Tactic[]
folders: Folder[]
currentFolder: number
}) {
const [showPopup, setShowPopup] = useState(false)
return (
<div
id="personal-space"
@ -101,7 +123,31 @@ function PersonalSpace({
width: width + "%",
}}>
<TitlePersonalSpace />
<BodyPersonalSpace allTactics={allTactics} />
<NewFolder showPopup={showPopup} setShowPopup={setShowPopup} currentFolder={currentFolder}/>
<BodyPersonalSpace tactics={tactics} folders={folders} />
</div>
)
}
function NewFolder({
showPopup,
setShowPopup,
currentFolder
}: { showPopup, setShowPopup: (newVal: boolean) => void, currentFolder: number }) {
return (
<div>
<div
id="new-folder-button"
onClick={() => setShowPopup(true)}
>Nouveau dossier</div>
<Popup displayState={showPopup} onClose={() => setShowPopup(false)}>
<h2>Nouveau dossier</h2>
<form action={location.pathname + BASE + "folder/" + currentFolder + "/new"} method="post" id="new-folder-form">
<label for="folderName">Nom du dossier</label>
<input type="text" id="folderName" name="folderName" required/>
<input type="submit" value="Confirmer" id="submit-form"/>
</form>
</Popup>
</div>
)
}
@ -114,15 +160,20 @@ function TitlePersonalSpace() {
)
}
function TableData({ allTactics }: { allTactics: Tactic[] }) {
const nbRow = Math.floor(allTactics.length / 3) + 1
let listTactic = Array(nbRow)
for (let i = 0; i < nbRow; i++) {
function TableData({ tactics,folders }: { tactics: Tactic[],folders: Folder[]}) {
const nbTacticRow = Math.floor(tactics.length / 3) + 1
const nbFolderRow = Math.floor(folders.length / 3) + 1
let listTactic = Array(nbTacticRow)
let listFolder = Array(nbFolderRow)
for (let i = 0; i < nbTacticRow; i++) {
listTactic[i] = Array(0)
}
for (let i = 0; i < nbFolderRow ; i++) {
listFolder[i] = Array(0)
}
let i = 0
let j = 0
allTactics.forEach((tactic) => {
tactics.forEach((tactic) => {
listTactic[i].push(tactic)
j++
if (j === 3) {
@ -130,9 +181,17 @@ function TableData({ allTactics }: { allTactics: Tactic[] }) {
j = 0
}
})
folders.forEach((folder) => {
listFolder[i].push(folder)
j++
if (j === 3) {
i++
j = 0
}
})
i = 0
while (i < nbRow) {
while (i < nbTacticRow) {
listTactic[i] = listTactic[i].map((tactic: Tactic) => (
<td
key={tactic.id}
@ -145,26 +204,49 @@ function TableData({ allTactics }: { allTactics: Tactic[] }) {
))
i++
}
if (nbRow == 1) {
i = 0
while (i < nbFolderRow) {
listFolder[i] = listFolder[i].map((folder: Folder) => (
<td
key={folder.id}
className="data"
onClick={() => {
location.pathname = BASE + "/tactic/" + folder.id + "/edit"
}}>
{truncateString(folder.name, 25)}
</td>
))
i++
}
if (nbTacticRow == 1) {
if (listTactic[0].length < 3) {
for (let i = 0; i <= 3 - listTactic[0].length; i++) {
listTactic[0].push(<td key={"tdNone" + i}></td>)
}
}
}
if (nbFolderRow == 1) {
if (listFolder[0].length < 3) {
for (let i = 0; i <= 3 - listFolder[0].length; i++) {
listFolder[0].push(<td key={"tdNone" + i}></td>)
}
}
}
const data = listTactic.map((tactic, rowIndex) => (
return listTactic.map((tactic, rowIndex) => (
<tr key={rowIndex + "row"}>{tactic}</tr>
))
return data
)).concat(listFolder.map((folder, rowIndex) => (
<tr key={rowIndex + "row"}>{folder}</tr>
)))
}
function BodyPersonalSpace({ allTactics }: { allTactics: Tactic[] }) {
function BodyPersonalSpace({ tactics,folders }: { tactics: Tactic[],folders: Folder[]}) {
let data
if (allTactics.length == 0) {
if (tactics.length == 0) {
data = <p>Aucune tactique créée !</p>
} else {
data = <TableData allTactics={allTactics} />
data = <TableData tactics={tactics} folders={folders}/>
}
return (

@ -38,7 +38,7 @@ function getConnection(): Connection {
}
function getUserController(): UserController {
return new UserController(new TacticModel(new TacticInfoGateway(getConnection())), new TeamModel(new TeamGateway(getConnection()), new MemberGateway(getConnection()), new AccountGateway(getConnection())));
return new UserController(new TacticModel(new TacticInfoGateway(getConnection())), new TeamModel(new TeamGateway(getConnection()), new MemberGateway(getConnection()), new AccountGateway(getConnection())),new \IQBall\Core\Model\PersonalSpaceModel(new \IQBall\Core\Gateway\PersonalSpaceGateway(getConnection()),new TacticInfoGateway(getConnection())));
}
function getVisualizerController(): VisualizerController {
@ -86,6 +86,8 @@ function getRoutes(): AltoRouter {
$ar->map("GET", "/settings", Action::auth(fn(SessionHandle $s) => getUserController()->settings($s)));
$ar->map("GET", "/disconnect", Action::auth(fn(MutableSessionHandle $s) => getUserController()->disconnect($s)));
//folder-related
$ar->map("POST", "/folder/[i:idParent]/new", Action::auth(fn(int $id,SessionHandle $s) => getUserController()->createFolder($s,$_POST,$id)));
//tactic-related
$ar->map("GET", "/tactic/[i:id]/view", Action::auth(fn(int $id, SessionHandle $s) => getVisualizerController()->openVisualizer($id, $s)));

@ -4,6 +4,9 @@ DROP TABLE IF EXISTS Tactic;
DROP TABLE IF EXISTS Team;
DROP TABLE IF EXISTS User;
DROP TABLE IF EXISTS Member;
DROP TABLE IF EXISTS TacticFolder;
DROP TABLE IF EXISTS TacticFolderLink;
--
CREATE TABLE Account
(
id integer PRIMARY KEY AUTOINCREMENT,
@ -25,12 +28,23 @@ CREATE TABLE Tactic
FOREIGN KEY (owner) REFERENCES Account
);
CREATE TABLE FormEntries
CREATE TABLE TacticFolder
(
id integer PRIMARY KEY AUTOINCREMENT,
name varchar NOT NULL,
description varchar NOT NULL
owner integer NOT NULL,
tactic_folder_parent integer,
FOREIGN KEY (owner) REFERENCES Account,
FOREIGN KEY (tactic_folder_parent) REFERENCES TacticFolder
);
CREATE TABLE TacticFolderLink
(
id_folder integer NOT NULL,
id_tactic integer NOT NULL,
FOREIGN KEY (id_folder) REFERENCES TacticFolder,
FOREIGN KEY (id_tactic) REFERENCES Tactic
);
CREATE TABLE Team
(

@ -5,22 +5,27 @@ namespace IQBall\App\Controller;
use IQBall\App\Session\MutableSessionHandle;
use IQBall\App\Session\SessionHandle;
use IQBall\App\ViewHttpResponse;
use IQBall\Core\Http\HttpRequest;
use IQBall\Core\Http\HttpResponse;
use IQBall\Core\Model\PersonalSpaceModel;
use IQBall\Core\Model\TacticModel;
use IQBall\Core\Model\TeamModel;
use IQBall\Core\Validation\DefaultValidators;
class UserController {
private TacticModel $tactics;
private ?TeamModel $teams;
private PersonalSpaceModel $personalSpace;
/**
* @param TacticModel $tactics
* @param TeamModel|null $teams
* @param PersonalSpaceModel $personalSpace
*/
public function __construct(TacticModel $tactics, ?TeamModel $teams = null) {
public function __construct(TacticModel $tactics, ?TeamModel $teams, PersonalSpaceModel $personalSpace) {
$this->tactics = $tactics;
$this->teams = $teams;
$this->personalSpace = $personalSpace;
}
/**
@ -28,14 +33,15 @@ class UserController {
* @return ViewHttpResponse the home page view
*/
public function home(SessionHandle $session): ViewHttpResponse {
$rootFolder = 0;
$limitNbTactics = 5;
$user = $session->getAccount()->getUser();
$lastTactics = $this->tactics->getLast($limitNbTactics, $user->getId());
$allTactics = $this->tactics->getAll($user->getId());
$rootTactics = $this->personalSpace->getTacticFromFolder($user->getId());
$rootFolders = $this->personalSpace->getFolderFromFolder($user->getId());
$name = $user->getName();
if ($this->teams != null) {
$teams = $this->teams->getAll($user->getId());
} else {
@ -44,9 +50,11 @@ class UserController {
return ViewHttpResponse::react("views/Home.tsx", [
"lastTactics" => $lastTactics,
"allTactics" => $allTactics,
"allTactics" => $rootTactics,
"folders" => $rootFolders,
"teams" => $teams,
"username" => $name,
"currentFolder" => $rootFolder
]);
}
@ -62,4 +70,11 @@ class UserController {
return HttpResponse::redirect("/");
}
public function createFolder(SessionHandle $session,array $request,int $idParentFolder): HttpResponse{
$this->personalSpace->createFolder($request['folderName'],$session->getAccount()->getUser()->getId(),$idParentFolder);
return HttpResponse::redirect("/");
}
}

@ -0,0 +1,34 @@
<?php
namespace IQBall\Core\Data\PersonalSpace;
class PersonalSpaceFolder implements PersonalSpaceItem {
private int $id;
private string $name;
/**
* @param int $id
* @param string $name
*/
public function __construct(int $id, string $name) {
$this->id = $id;
$this->name = $name;
}
/**
* @return int
*/
public function getId(): int {
return $this->id;
}
/**
* @return string
*/
public function getName(): string {
return $this->name;
}
}

@ -0,0 +1,9 @@
<?php
namespace IQBall\Core\Data\PersonalSpace;
interface PersonalSpaceItem {
public function getId();
public function getName();
}

@ -1,8 +1,9 @@
<?php
namespace IQBall\Core\Data;
use IQBall\Core\Data\PersonalSpace\PersonalSpaceItem;
class TacticInfo {
class TacticInfo implements PersonalSpaceItem {
private int $id;
private string $name;
private int $creationDate;

@ -0,0 +1,100 @@
<?php
namespace IQBall\Core\Gateway;
use IQBall\Core\Connection;
use IQBall\Core\Data\PersonalSpace\PersonalSpaceFolder;
use IQBall\Core\Data\TacticInfo;
use PDO;
use IQBall\Core\Data\CourtType;
class PersonalSpaceGateway {
private Connection $con;
public function __construct(Connection $con) {
$this->con = $con;
}
public function addFolder(string $folderName, int $ownerId, int $parentId): void {
$this->con->exec("INSERT INTO TacticFolder(name,owner,tactic_folder_parent) VALUES(:name,:owner,:parent)",
[
"name" => [$folderName, \PDO::PARAM_STR],
"owner" => [$ownerId, \PDO::PARAM_INT],
"parent" => [$parentId, \PDO::PARAM_INT]
]
);
}
/**
* Return all the tactic of a specific folder
* If the folder's id is 0, we return all tactics at the root
* @param int $accountId
* @param int $folderId
* @return array
*/
public function getFolder(int $accountId, int $folderId): array {
if ($folderId == 0) {
$folders = $this->getFolderFromRoot($accountId);
} else {
$folders = $this->getFolderFromFolder($accountId, $folderId);
}
return $folders;
}
public function getTactic(int $accountId, int $folderId):array{
if ($folderId == 0) {
$tactics = $this->getTacticFromRoot($accountId);
} else {
$tactics = $this->getTacticFromFolder($accountId, $folderId);
}
return $tactics;
}
private function getTacticFromRoot(int $accountId): array {
$result = $this->con->fetch(
"SELECT t.*
FROM Tactic t
WHERE t.owner = :ownerId AND t.id NOT IN (SELECT id_tactic FROM TacticFolderLink)",
["ownerId" => [$accountId, PDO::PARAM_INT]]
);
return array_map(fn($row) => new TacticInfo($row['id'],$row['name'],$row['creation_date'],$row['owner'],CourtType::fromName($row['court_type']),$row['content']),$result);
}
private function getFolderFromRoot(int $accountId): array {
$result = $this->con->fetch(
"SELECT *
FROM TacticFolder
WHERE owner = :owner AND tactic_folder_parent IS NULL",
["ownerId" => [$accountId, PDO::PARAM_INT]]
);
var_dump($result);
return array_map(fn($row) => new PersonalSpaceFolder($row['id'],$row['name']),$result);
}
private function getTacticFromFolder(int $accountId, int $folderId): array {
$result = $this->con->fetch(
"SELECT t.*
FROM Tactic t, TacticFolderLink tfl
WHERE t.owner = :ownerId AND tfl.id_tactic = :folderId",
[
"ownerId" => [$accountId, PDO::PARAM_INT],
"folderId" => [$folderId, PDO::PARAM_INT]
]
);
return array_map(fn($row) => new TacticInfo($row['id'],$row['name'],$row['creation_date'],$row['owner'],CourtType::fromName($row['court_type']),$row['content']),$result);
}
private function getFolderFromFolder(int $accountId, int $folderId): array {
$result = $this->con->fetch(
"SELECT *
FROM TacticFolder
WHERE owner = :ownerId AND tactic_folder_parent = :folderId",
[
"ownerId" => [$accountId, PDO::PARAM_INT],
"folderId" => [$folderId, PDO::PARAM_INT]
]
);
return array_map(fn($row) => new PersonalSpaceFolder($row['id'],$row['name']),$result);
}
}

@ -130,4 +130,7 @@ class TacticInfoGateway {
]);
return $stmnt->rowCount() == 1;
}
}

@ -0,0 +1,33 @@
<?php
namespace IQBall\Core\Model;
use IQBall\Core\Gateway\PersonalSpaceGateway;
use IQBall\Core\Gateway\TacticInfoGateway;
class PersonalSpaceModel {
private PersonalSpaceGateway $personalSpaces;
private TacticInfoGateway $tactics;
/**
* @param PersonalSpaceGateway $personalSpaces
* @param TacticInfoGateway $tactics
*/
public function __construct(PersonalSpaceGateway $personalSpaces,TacticInfoGateway $tactics) {
$this->personalSpaces = $personalSpaces;
$this->tactics = $tactics;
}
public function createFolder(string $folderName,int $ownerId,int $parentFolder): void{
$this->personalSpaces->addFolder($folderName,$ownerId,$parentFolder);
}
public function getFolderFromFolder(int $accountId,int $folderId = 0): array{
return $this->personalSpaces->getFolder($accountId,$folderId);
}
public function getTacticFromFolder(int $accountId,int $folderId = 0): array{
return $this->personalSpaces->getTactic($accountId,$folderId);
}
}
Loading…
Cancel
Save