mirror of
https://git.rezel.net/LudoTech/traque.git
synced 2026-02-09 10:20:16 +01:00
improved admin UI
This commit is contained in:
@@ -7,13 +7,11 @@ export default function AdminLayout({ children}) {
|
|||||||
<AdminConnexionProvider>
|
<AdminConnexionProvider>
|
||||||
<AdminProvider>
|
<AdminProvider>
|
||||||
<div className='h-full flex flex-col'>
|
<div className='h-full flex flex-col'>
|
||||||
<div className="text-lg max-h-10 p-5 bg-gray-800 text-white flex items-center justify-left">
|
<div className="text-xl max-h-15 bg-gray-800 text-white flex items-center justify-left">
|
||||||
<div className="mx-5">Admin</div>
|
<ul className='flex' >
|
||||||
<ul className='flex space-x-4'>
|
<li className="p-5 bg-gray-800 hover:bg-gray-600 transition-all cursor-pointer h-full"><Link href="/admin">Admin</Link></li>
|
||||||
<li><Link href="/admin">Home</Link></li>
|
<li className="p-5 bg-gray-800 hover:bg-gray-600 transition-all cursor-pointer h-full"><Link href="/admin/teams">Teams</Link></li>
|
||||||
<li><Link href="/admin/teams">Teams</Link></li>
|
</ul>
|
||||||
<li><Link href="/admin/map">Map</Link></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="h-full overflow-y-scroll">
|
<div className="h-full overflow-y-scroll">
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -9,21 +9,29 @@ import dynamic from "next/dynamic";
|
|||||||
const ZoneSelector = dynamic(() => import('@/components/admin/zoneSelector').then((mod) => mod.ZoneSelector), {
|
const ZoneSelector = dynamic(() => import('@/components/admin/zoneSelector').then((mod) => mod.ZoneSelector), {
|
||||||
ssr: false
|
ssr: false
|
||||||
});
|
});
|
||||||
|
const LiveMap = dynamic(() => import('@/components/admin/mapPicker').then((mod) => mod.LiveMap), {
|
||||||
|
ssr: false
|
||||||
|
});
|
||||||
export default function AdminPage() {
|
export default function AdminPage() {
|
||||||
const { useProtect } = useAdminConnexion();
|
const { useProtect } = useAdminConnexion();
|
||||||
const { gameState, changeState } = useAdmin();
|
const { gameState, changeState } = useAdmin();
|
||||||
useProtect();
|
useProtect();
|
||||||
return (
|
return (
|
||||||
<div className='min-h-full bg-gray-200 p-10 flex flex-row flex-wrap content-start gap-5'>
|
<div className='min-h-full bg-gray-200 p-10 flex flex-row content-start gap-5'>
|
||||||
<div className='w-max h-1/2 gap-3 bg-gray-200 p-10 flex flex-col text-center shadow-2xl '>
|
<div className="h-full">
|
||||||
<h2 className="text-2xl">Game state </h2>
|
<div className='w-max h-1/2 gap-3 bg-white p-10 flex flex-col text-center shadow-2xl '>
|
||||||
<strong className="p-5 bg-gray-900 text-white text-xl rounded">Current : {gameState}</strong>
|
<h2 className="text-2xl">Game state </h2>
|
||||||
<RedButton onClick={() => changeState(GameState.SETUP)}>Reset game</RedButton>
|
<strong className="p-5 bg-gray-900 text-white text-xl rounded">Current : {gameState}</strong>
|
||||||
<GreenButton onClick={() => changeState(GameState.PLACEMENT)}>Start placement</GreenButton>
|
<RedButton onClick={() => changeState(GameState.SETUP)}>Reset game</RedButton>
|
||||||
<BlueButton onClick={() => changeState(GameState.PLAYING)}>Start game</BlueButton>
|
<GreenButton onClick={() => changeState(GameState.PLACEMENT)}>Start placement</GreenButton>
|
||||||
|
<BlueButton onClick={() => changeState(GameState.PLAYING)}>Start game</BlueButton>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{gameState == GameState.PLACEMENT && <div className="max-h-5/6"><TeamReady /></div>}
|
{gameState == GameState.PLACEMENT && <div className="max-h-5/6"><TeamReady /></div>}
|
||||||
{(gameState == GameState.SETUP || gameState == GameState.PLACEMENT) && <ZoneSelector />}
|
{(gameState == GameState.SETUP || gameState == GameState.PLACEMENT) && <ZoneSelector />}
|
||||||
|
{gameState == GameState.PLAYING && <div className='grow flex-1 row-span-2 bg-white p-10 flex shadow-2xl'>
|
||||||
|
<LiveMap />
|
||||||
|
</div>}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -2,8 +2,9 @@
|
|||||||
import { useLocation } from "@/hook/useLocation";
|
import { useLocation } from "@/hook/useLocation";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import "leaflet/dist/leaflet.css";
|
import "leaflet/dist/leaflet.css";
|
||||||
import { Circle, MapContainer, Marker, TileLayer, useMap } from "react-leaflet";
|
import { Circle, MapContainer, Marker, Popup, TileLayer, useMap } from "react-leaflet";
|
||||||
import { useMapCircleDraw } from "@/hook/mapDrawing";
|
import { useMapCircleDraw } from "@/hook/mapDrawing";
|
||||||
|
import useAdmin from "@/hook/useAdmin";
|
||||||
|
|
||||||
function MapPan(props) {
|
function MapPan(props) {
|
||||||
const map = useMap();
|
const map = useMap();
|
||||||
@@ -91,4 +92,34 @@ export function ZonePicker({ minZone, setMinZone, maxZone, setMaxZone, editMode,
|
|||||||
<MapEventListener onClick={handleClick} onMouseMove={handleMouseMove} />
|
<MapEventListener onClick={handleClick} onMouseMove={handleMouseMove} />
|
||||||
</MapContainer>
|
</MapContainer>
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function LiveMap() {
|
||||||
|
const location = useLocation(Infinity);
|
||||||
|
const { zone, zoneExtremities, teams, getTeamName } = useAdmin();
|
||||||
|
return (
|
||||||
|
<MapContainer className='min-h-full w-full ' center={location} zoom={DEFAULT_ZOOM} scrollWheelZoom={true}>
|
||||||
|
<TileLayer
|
||||||
|
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||||
|
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||||
|
/>
|
||||||
|
<MapPan center={location} zoom={DEFAULT_ZOOM} />
|
||||||
|
{zone && <Circle center={zone.center} radius={zone.radius} color="blue" />}
|
||||||
|
{zoneExtremities && <Circle center={zoneExtremities.begin.center} radius={zoneExtremities.begin.radius} color='black' fill={false} />}
|
||||||
|
{zoneExtremities && <Circle center={zoneExtremities.end.center} radius={zoneExtremities.end.radius} color='red' fill={false} />}
|
||||||
|
{teams.map((team) => team.currentLocation && !team.captured && <Marker key={team.id} position={team.currentLocation} icon={new L.Icon({
|
||||||
|
iconUrl: '/icons/location.png',
|
||||||
|
iconSize: [41, 41],
|
||||||
|
iconAnchor: [12, 41],
|
||||||
|
popupAnchor: [1, -34],
|
||||||
|
shadowSize: [41, 41]
|
||||||
|
})}>
|
||||||
|
<Popup>
|
||||||
|
<strong className="text-lg">{team.name}</strong>
|
||||||
|
<p className="text-md">Chasing : {getTeamName(team.chasing)}</p>
|
||||||
|
<p className="text-md">Chased by : {getTeamName(team.chased)}</p>
|
||||||
|
</Popup>
|
||||||
|
</Marker>)}
|
||||||
|
</MapContainer>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,7 @@ import useAdmin from "@/hook/useAdmin"
|
|||||||
|
|
||||||
export function TeamReady() {
|
export function TeamReady() {
|
||||||
const {teams} = useAdmin();
|
const {teams} = useAdmin();
|
||||||
return <div className='w-max h-full gap-1 bg-gray-200 p-10 flex flex-col text-center shadow-2xl overflow-y-scroll'>
|
return <div className='w-max h-full gap-1 bg-white p-10 flex flex-col text-center shadow-2xl overflow-y-scroll'>
|
||||||
<h2 className="text-2xl">Teams ready status</h2>
|
<h2 className="text-2xl">Teams ready status</h2>
|
||||||
{teams.map((team) => team.ready ? (
|
{teams.map((team) => team.ready ? (
|
||||||
<div key={team.id} className="p-2 text-white bg-green-500 shadow-md text-xl rounded flex flex-row">
|
<div key={team.id} className="p-2 text-white bg-green-500 shadow-md text-xl rounded flex flex-row">
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export function ZoneSelector() {
|
|||||||
|
|
||||||
}, [minZone, maxZone]);
|
}, [minZone, maxZone]);
|
||||||
|
|
||||||
return <div className='w-2/5 h-full gap-1 bg-gray-200 p-10 flex flex-col text-center shadow-2xl overflow-y-scroll'>
|
return <div className='w-2/5 h-full gap-1 bg-white p-10 flex flex-col text-center shadow-2xl overflow-y-scroll'>
|
||||||
<h2 className="text-2xl">Edit zones</h2>
|
<h2 className="text-2xl">Edit zones</h2>
|
||||||
{editMode == EditMode.MIN && <RedButton onClick={() => setEditMode(EditMode.MAX)}>Edit end zone</RedButton>}
|
{editMode == EditMode.MIN && <RedButton onClick={() => setEditMode(EditMode.MAX)}>Edit end zone</RedButton>}
|
||||||
{editMode == EditMode.MAX && <BlueButton onClick={() => setEditMode(EditMode.MIN)}>Edit start zone</BlueButton>}
|
{editMode == EditMode.MAX && <BlueButton onClick={() => setEditMode(EditMode.MIN)}>Edit start zone</BlueButton>}
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ const adminContext = createContext();
|
|||||||
function AdminProvider({ children }) {
|
function AdminProvider({ children }) {
|
||||||
const [teams, setTeams] = useState([]);
|
const [teams, setTeams] = useState([]);
|
||||||
const [zoneSettings, setZoneSettings] = useState(null)
|
const [zoneSettings, setZoneSettings] = useState(null)
|
||||||
|
const [zone, setZone] = useState(null);
|
||||||
|
const [zoneExtremities, setZoneExtremities] = useState(null);
|
||||||
const { adminSocket } = useSocket();
|
const { adminSocket } = useSocket();
|
||||||
const { loggedIn } = useAdminConnexion();
|
const { loggedIn } = useAdminConnexion();
|
||||||
const [gameState, setGameState] = useState(GameState.SETUP);
|
const [gameState, setGameState] = useState(GameState.SETUP);
|
||||||
@@ -23,8 +25,10 @@ function AdminProvider({ children }) {
|
|||||||
//Bind listeners to update the team list and the game status on socket message
|
//Bind listeners to update the team list and the game status on socket message
|
||||||
useSocketListener(adminSocket, "teams", setTeams);
|
useSocketListener(adminSocket, "teams", setTeams);
|
||||||
useSocketListener(adminSocket, "zone_settings", setZoneSettings);
|
useSocketListener(adminSocket, "zone_settings", setZoneSettings);
|
||||||
|
useSocketListener(adminSocket, "zone", setZone);
|
||||||
|
useSocketListener(adminSocket, "new_zone", setZoneExtremities);
|
||||||
|
|
||||||
const value = useMemo(() => ({ teams, zoneSettings, setZoneSettings, setTeams, gameState }), [zoneSettings, teams, gameState]);
|
const value = useMemo(() => ({ zone, zoneExtremities, teams, zoneSettings, setZoneSettings, setTeams, gameState }), [zoneSettings, teams, gameState, zone, zoneExtremities]);
|
||||||
return (
|
return (
|
||||||
<adminContext.Provider value={value}>
|
<adminContext.Provider value={value}>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useAdminContext } from "@/context/adminContext";
|
|||||||
import { useSocket } from "@/context/socketContext";
|
import { useSocket } from "@/context/socketContext";
|
||||||
|
|
||||||
export default function useAdmin(){
|
export default function useAdmin(){
|
||||||
const {teams, gameState, zoneSettings } = useAdminContext();
|
const {teams, gameState, zoneSettings, zone, zoneExtremities } = useAdminContext();
|
||||||
const {adminSocket} = useSocket();
|
const {adminSocket} = useSocket();
|
||||||
|
|
||||||
function pollTeams() {
|
function pollTeams() {
|
||||||
@@ -42,6 +42,6 @@ export default function useAdmin(){
|
|||||||
adminSocket.emit("set_zone_settings", zone);
|
adminSocket.emit("set_zone_settings", zone);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {teams, zoneSettings, gameState,changeZoneSettings, pollTeams, getTeam, getTeamName, reorderTeams, addTeam, removeTeam, changeState, updateTeam };
|
return {teams, zoneSettings, gameState, zone, zoneExtremities,changeZoneSettings, pollTeams, getTeam, getTeamName, reorderTeams, addTeam, removeTeam, changeState, updateTeam };
|
||||||
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user