You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
445 lines
13 KiB
445 lines
13 KiB
import "../style/home/home.css"
|
|
import { useNavigate } from "react-router-dom"
|
|
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
|
|
name: string
|
|
creationDate: number
|
|
}
|
|
|
|
interface Team {
|
|
id: number
|
|
name: string
|
|
picture: string
|
|
main_color: string
|
|
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<HomePageStateAction>
|
|
}
|
|
|
|
const HomeStateContext = createContext<HomeStateContextMutable | null>(null)
|
|
|
|
function useHomeState() {
|
|
return useContext(HomeStateContext)
|
|
}
|
|
|
|
export default function HomePage() {
|
|
const [state, dispatch] = useReducer(homePageStateReducer, {
|
|
tactics: [],
|
|
teams: [],
|
|
})
|
|
|
|
const navigate = useNavigate()
|
|
const fetcher = useAppFetcher()
|
|
|
|
useEffect(() => {
|
|
async function initUserData() {
|
|
const response = await fetcher.fetchAPIGet("user-data")
|
|
|
|
if (response.status == 401) {
|
|
navigate("/login")
|
|
return // if unauthorized
|
|
}
|
|
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])
|
|
|
|
const tacticExportService = useMemo(
|
|
() =>
|
|
state.exportingTacticId
|
|
? new APITacticService(fetcher, state.exportingTacticId!)
|
|
: null,
|
|
[fetcher, state.exportingTacticId],
|
|
)
|
|
|
|
return (
|
|
<HomeStateContext.Provider value={{ state, dispatch }}>
|
|
{tacticExportService && (
|
|
<div id="exports-popup">
|
|
<ExportTacticPopup
|
|
service={tacticExportService}
|
|
onHide={() =>
|
|
dispatch({
|
|
type: HomePageStateActionKind.SET_EXPORTING_TACTIC,
|
|
tacticId: undefined,
|
|
})
|
|
}
|
|
/>
|
|
</div>
|
|
)}
|
|
<Home />
|
|
</HomeStateContext.Provider>
|
|
)
|
|
}
|
|
|
|
function Home() {
|
|
return (
|
|
<div id="main">
|
|
<Body />
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function Body() {
|
|
const widthPersonalSpace = 78
|
|
const widthSideMenu = 100 - widthPersonalSpace
|
|
return (
|
|
<div id="body">
|
|
<PersonalSpace width={widthPersonalSpace} />
|
|
<SideMenu width={widthSideMenu} />
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function SideMenu({ width }: { width: number }) {
|
|
return (
|
|
<div
|
|
id="side-menu"
|
|
style={{
|
|
width: width + "%",
|
|
}}>
|
|
<div id="side-menu-content">
|
|
<LastTeamsSideMenu />
|
|
<LastTacticsSideMenu />
|
|
<TacticImportArea />
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
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 (
|
|
<div id="tactic-import-area">
|
|
<FileUploader
|
|
handleChange={handleDrop}
|
|
types={["json"]}
|
|
hoverTitle="Déposez ici"
|
|
label="Séléctionnez ou déposez un fichier ici"/>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function PersonalSpace({ width }: { width: number }) {
|
|
return (
|
|
<div
|
|
id="personal-space"
|
|
style={{
|
|
width: width + "%",
|
|
}}>
|
|
<TitlePersonalSpace />
|
|
<BodyPersonalSpace />
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function TitlePersonalSpace() {
|
|
return (
|
|
<div id="title-personal-space">
|
|
<h2>Espace Personnel</h2>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function TacticGrid({ tactics }: { tactics: Tactic[] }) {
|
|
return (
|
|
<div id={"tactics-grid"}>
|
|
{tactics.map((team) => (
|
|
<TacticCard key={team.id} tactic={team} />
|
|
))}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function TacticCard({ tactic }: { tactic: Tactic }) {
|
|
const navigate = useNavigate()
|
|
const fetcher = useAppFetcher()
|
|
const {
|
|
dispatch,
|
|
} = useHomeState()!
|
|
return (
|
|
<div
|
|
className={"tactic-card"}
|
|
onClick={() => navigate(`/tactic/${tactic.id}/edit`)}>
|
|
<div className={"tactic-card-preview"}>
|
|
<Visualizer
|
|
visualizerId={tactic.id.toString()}
|
|
tacticId={tactic.id}
|
|
/>
|
|
</div>
|
|
<div className="tactic-card-content">
|
|
<p className="tactic-card-title">{tactic.name}</p>
|
|
<div className="tactic-card-actions">
|
|
<ExportSvg
|
|
className="tactic-card-export-btn"
|
|
onClick={(e) => {
|
|
e.stopPropagation()
|
|
dispatch({
|
|
type: HomePageStateActionKind.SET_EXPORTING_TACTIC,
|
|
tacticId: tactic.id,
|
|
})
|
|
}}
|
|
/>
|
|
<BinSvg
|
|
className="tactic-card-remove-btn"
|
|
onClick={async (e) => {
|
|
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,
|
|
})
|
|
}}
|
|
/>
|
|
<DuplicateSvg
|
|
className="tactic-card-duplicate-btn"
|
|
onClick={async (e) => {
|
|
e.stopPropagation()
|
|
const service = new APITacticService(
|
|
fetcher,
|
|
tactic.id,
|
|
)
|
|
const context = await service.getContext()
|
|
if (typeof context === "string")
|
|
throw Error(context)
|
|
|
|
const plainTactic = await loadPlainTactic(
|
|
context,
|
|
service,
|
|
)
|
|
const { name, id } = await importTactic(
|
|
fetcher,
|
|
plainTactic,
|
|
)
|
|
|
|
dispatch({
|
|
type: HomePageStateActionKind.ADD_TACTIC,
|
|
tactic: { name, id, creationDate: 0 },
|
|
})
|
|
}}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function BodyPersonalSpace() {
|
|
const tactics = useHomeState()!.state.tactics
|
|
return (
|
|
<div id="body-personal-space">
|
|
{tactics.length == 0 ? (
|
|
<p>Aucune tactique créée !</p>
|
|
) : (
|
|
<TacticGrid tactics={tactics} />
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function LastTeamsSideMenu() {
|
|
const navigate = useNavigate()
|
|
return (
|
|
<div id="teams">
|
|
<div className="titre-side-menu">
|
|
<h2 className="title">Mes équipes</h2>
|
|
<button className="new" onClick={() => navigate("/team/new")}>
|
|
+
|
|
</button>
|
|
</div>
|
|
<SetButtonTeam />
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function LastTacticsSideMenu() {
|
|
const navigate = useNavigate()
|
|
|
|
return (
|
|
<div id="tactic">
|
|
<div className="titre-side-menu">
|
|
<h2 className="title">Mes dernières tactiques</h2>
|
|
<button
|
|
className="new"
|
|
id="create-tactic"
|
|
onClick={() => navigate("/tactic/new")}>
|
|
+
|
|
</button>
|
|
</div>
|
|
<SetButtonTactic />
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function SetButtonTactic() {
|
|
const tactics = useHomeState()!.state.tactics.slice(0, 5)
|
|
const lastTactics = tactics.map((tactic) => (
|
|
<LastTacticCard key={tactic.id} tactic={tactic} />
|
|
))
|
|
return <div className="set-button">{lastTactics}</div>
|
|
}
|
|
|
|
function SetButtonTeam() {
|
|
const teams = useHomeState()!.state.teams
|
|
|
|
const listTeam = teams.map((team) => <TeamCard key={team.id} team={team} />)
|
|
return <div className="set-button">{listTeam}</div>
|
|
}
|
|
|
|
function TeamCard({ team }: { team: Team }) {
|
|
const name = truncateString(team.name, 20)
|
|
const navigate = useNavigate()
|
|
|
|
return (
|
|
<div>
|
|
<div
|
|
id={"button-team" + team.id}
|
|
className="button-side-menu data"
|
|
onClick={() => {
|
|
navigate("/team/" + team.id)
|
|
}}>
|
|
{name}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function LastTacticCard({ tactic }: { tactic: Tactic }) {
|
|
const name = truncateString(tactic.name, 20)
|
|
const navigate = useNavigate()
|
|
|
|
return (
|
|
<div
|
|
id={"button" + tactic.id}
|
|
className="button-side-menu data"
|
|
onClick={() => {
|
|
navigate("/tactic/" + tactic.id + "/edit")
|
|
}}>
|
|
{name}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function truncateString(name: string, limit: number): string {
|
|
if (name.length > limit) {
|
|
name = name.substring(0, limit) + "..."
|
|
}
|
|
return name
|
|
}
|