+ {showExportPopup && (
+
+ )}
@@ -835,10 +852,15 @@ function EditorPage({
VISUALISER
+
diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx
index 1a0d656..57bacb5 100644
--- a/src/pages/HomePage.tsx
+++ b/src/pages/HomePage.tsx
@@ -1,8 +1,15 @@
import "../style/home/home.css"
import { useNavigate } from "react-router-dom"
-import { useEffect, useState } from "react"
-import { User } from "../model/User.ts"
+import { createContext, Dispatch, useCallback, useContext, useEffect, useMemo, useReducer } from "react"
import { useAppFetcher } from "../App.tsx"
+import { Visualizer } from "../components/Visualizer.tsx"
+import BinSvg from "../assets/icon/bin.svg?react"
+import ExportSvg from "../assets/icon/export.svg?react"
+import DuplicateSvg from "../assets/icon/duplicate.svg?react"
+import ExportTacticPopup from "./popup/ExportTacticPopup.tsx"
+import { APITacticService } from "../service/APITacticService.ts"
+import { FileUploader } from "react-drag-drop-files"
+import { importTactic, importTacticFromFile, loadPlainTactic } from "../domains/TacticPersistenceDomain.ts"
interface Tactic {
id: number
@@ -18,9 +25,87 @@ interface Team {
second_color: string
}
+enum HomePageStateActionKind {
+ UPDATE_TACTICS = "UPDATE_TACTICS",
+ ADD_TACTIC = "ADD_TACTIC",
+ REMOVE_TACTIC = "REMOVE_TACTIC",
+ UPDATE_TEAMS = "UPDATE_TEAMS",
+ SET_EXPORTING_TACTIC = "SET_EXPORTING_TACTIC",
+ INIT = "INIT",
+}
+
+type HomePageStateAction =
+ | {
+ type: HomePageStateActionKind.UPDATE_TACTICS
+ tactics: Tactic[]
+}
+ | {
+ type: HomePageStateActionKind.UPDATE_TEAMS
+ teams: Team[]
+}
+ | {
+ type: HomePageStateActionKind.INIT
+ state: HomePageState
+}
+ | {
+ type: HomePageStateActionKind.SET_EXPORTING_TACTIC
+ tacticId: number | undefined
+}
+ | {
+ type: HomePageStateActionKind.REMOVE_TACTIC,
+ tacticId: number
+} | {
+ type: HomePageStateActionKind.ADD_TACTIC,
+ tactic: Tactic
+}
+
+interface HomePageState {
+ tactics: Tactic[]
+ teams: Team[]
+ /**
+ * The home page displays a popup to export a certains tactic
+ */
+ exportingTacticId?: number
+}
+
+function homePageStateReducer(
+ state: HomePageState,
+ action: HomePageStateAction,
+): HomePageState {
+ switch (action.type) {
+ case HomePageStateActionKind.UPDATE_TACTICS:
+ return { ...state, tactics: action.tactics }
+
+ case HomePageStateActionKind.UPDATE_TEAMS:
+ return { ...state, teams: action.teams }
+
+ case HomePageStateActionKind.SET_EXPORTING_TACTIC:
+ return { ...state, exportingTacticId: action.tacticId }
+
+ case HomePageStateActionKind.ADD_TACTIC:
+ return { ...state, tactics: [action.tactic, ...state.tactics] }
+
+ case HomePageStateActionKind.REMOVE_TACTIC:
+ return { ...state, tactics: state.tactics.filter(t => t.id !== action.tacticId) }
+
+ case HomePageStateActionKind.INIT:
+ return action.state
+ }
+}
+
+interface HomeStateContextMutable {
+ state: HomePageState
+ dispatch: Dispatch
+}
+
+const HomeStateContext = createContext(null)
+
+function useHomeState() {
+ return useContext(HomeStateContext)
+}
+
export default function HomePage() {
- type UserDataResponse = { user?: User; tactics: Tactic[]; teams: Team[] }
- const [{ tactics, teams }, setInfo] = useState({
+ const [state, dispatch] = useReducer(homePageStateReducer, {
tactics: [],
teams: [],
})
@@ -31,76 +116,71 @@ export default function HomePage() {
useEffect(() => {
async function initUserData() {
const response = await fetcher.fetchAPIGet("user-data")
+
if (response.status == 401) {
navigate("/login")
return // if unauthorized
}
- setInfo(await response.json())
+ type UserDataResponse = { teams: Team[]; tactics: Tactic[] }
+ const { teams, tactics }: UserDataResponse = await response.json()
+ tactics.sort((a, b) => b.creationDate - a.creationDate)
+ dispatch({
+ type: HomePageStateActionKind.INIT,
+ state: { teams, tactics },
+ })
}
initUserData()
}, [fetcher, navigate])
- tactics!.sort((a, b) => b.creationDate - a.creationDate)
+ const tacticExportService = useMemo(
+ () =>
+ state.exportingTacticId
+ ? new APITacticService(fetcher, state.exportingTacticId!)
+ : null,
+ [fetcher, state.exportingTacticId],
+ )
- const lastTactics = tactics.slice(0, 5)
return (
-
+
+ {tacticExportService && (
+
+ )}
+
+
)
}
-function Home({
- lastTactics,
- allTactics,
- teams,
-}: {
- lastTactics: Tactic[]
- allTactics: Tactic[]
- teams: Team[]
-}) {
+function Home() {
return (
-
+
)
}
-function Body({
- lastTactics,
- allTactics,
- teams,
-}: {
- lastTactics: Tactic[]
- allTactics: Tactic[]
- teams: Team[]
-}) {
+function Body() {
const widthPersonalSpace = 78
const widthSideMenu = 100 - widthPersonalSpace
return (
)
}
-function SideMenu({
- width,
- lastTactics,
- teams,
-}: {
- width: number
- lastTactics: Tactic[]
- teams: Team[]
-}) {
+function SideMenu({ width }: { width: number }) {
return (
)
}
-function PersonalSpace({
- width,
- allTactics,
-}: {
- width: number
- allTactics: Tactic[]
-}) {
+function TacticImportArea() {
+ const fetcher = useAppFetcher()
+ const { dispatch } = useHomeState()!
+
+ const handleDrop = useCallback(
+ async (file: File) => {
+ importTacticFromFile(fetcher, file, (tactic) => {
+ dispatch({
+ type: HomePageStateActionKind.ADD_TACTIC,
+ tactic: {
+ name: tactic.name,
+ id: tactic.id,
+ creationDate: new Date().getDate(),
+ },
+ })
+ })
+ },
+ [dispatch, fetcher],
+ )
+
+ return (
+
+
+
+ )
+}
+
+function PersonalSpace({ width }: { width: number }) {
return (
-
+
)
}
@@ -142,69 +248,112 @@ function TitlePersonalSpace() {
)
}
-function TableData({ allTactics }: { allTactics: Tactic[] }) {
- const nbRow = Math.floor(allTactics.length / 3) + 1
- const 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
- }
- })
+function TacticGrid({ tactics }: { tactics: Tactic[] }) {
+ return (
+
+ {tactics.map((team) => (
+
+ ))}
+
+ )
+}
+function TacticCard({ tactic }: { tactic: Tactic }) {
const navigate = useNavigate()
+ const fetcher = useAppFetcher()
+ const {
+ dispatch,
+ } = useHomeState()!
+ return (
+
navigate(`/tactic/${tactic.id}/edit`)}>
+
+
+
+
+
{tactic.name}
+
+ {
+ e.stopPropagation()
+ dispatch({
+ type: HomePageStateActionKind.SET_EXPORTING_TACTIC,
+ tacticId: tactic.id,
+ })
+ }}
+ />
+ {
+ e.stopPropagation()
+ const response = await fetcher.fetchAPI(
+ `tactics/${tactic.id}`,
+ {},
+ "DELETE",
+ )
+ if (!response.ok) {
+ throw Error(
+ `Cannot delete tactic ${tactic.id}!`,
+ )
+ }
+ dispatch({
+ type: HomePageStateActionKind.REMOVE_TACTIC,
+ tacticId: tactic.id,
+ })
+ }}
+ />
+ {
+ e.stopPropagation()
+ const service = new APITacticService(
+ fetcher,
+ tactic.id,
+ )
+ const context = await service.getContext()
+ if (typeof context === "string")
+ throw Error(context)
- i = 0
- while (i < nbRow) {
- listTactic[i] = listTactic[i].map((tactic: Tactic) => (
- {
- navigate("/tactic/" + tactic.id + "/edit")
- }}>
- {truncateString(tactic.name, 25)}
- |
- ))
- i++
- }
- if (nbRow == 1) {
- if (listTactic[0].length < 3) {
- for (let i = 0; i <= 3 - listTactic[0].length; i++) {
- listTactic[0].push( | )
- }
- }
- }
+ const plainTactic = await loadPlainTactic(
+ context,
+ service,
+ )
+ const { name, id } = await importTactic(
+ fetcher,
+ plainTactic,
+ )
- return listTactic.map((tactic, rowIndex) => (
- {tactic}
- ))
+ dispatch({
+ type: HomePageStateActionKind.ADD_TACTIC,
+ tactic: { name, id, creationDate: 0 },
+ })
+ }}
+ />
+
+
+
+ )
}
-function BodyPersonalSpace({ allTactics }: { allTactics: Tactic[] }) {
+function BodyPersonalSpace() {
+ const tactics = useHomeState()!.state.tactics
return (
- {allTactics.length == 0 ? (
+ {tactics.length == 0 ? (
Aucune tactique créée !
) : (
-
+
)}
)
}
-function Team({ teams }: { teams: Team[] }) {
+function LastTeamsSideMenu() {
const navigate = useNavigate()
return (
@@ -214,12 +363,12 @@ function Team({ teams }: { teams: Team[] }) {
+
-
+
)
}
-function Tactic({ lastTactics }: { lastTactics: Tactic[] }) {
+function LastTacticsSideMenu() {
const navigate = useNavigate()
return (
@@ -233,26 +382,27 @@ function Tactic({ lastTactics }: { lastTactics: Tactic[] }) {
+
-