improved admin UI

This commit is contained in:
Quentin Roussel
2024-04-20 15:15:58 +02:00
parent 11d5962e70
commit 6289f7cb88
7 changed files with 61 additions and 20 deletions

View File

@@ -7,13 +7,11 @@ export default function AdminLayout({ children}) {
<AdminConnexionProvider>
<AdminProvider>
<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="mx-5">Admin</div>
<ul className='flex space-x-4'>
<li><Link href="/admin">Home</Link></li>
<li><Link href="/admin/teams">Teams</Link></li>
<li><Link href="/admin/map">Map</Link></li>
</ul>
<div className="text-xl max-h-15 bg-gray-800 text-white flex items-center justify-left">
<ul className='flex' >
<li className="p-5 bg-gray-800 hover:bg-gray-600 transition-all cursor-pointer h-full"><Link href="/admin">Admin</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>
</ul>
</div>
<div className="h-full overflow-y-scroll">
{children}

View File

@@ -9,21 +9,29 @@ import dynamic from "next/dynamic";
const ZoneSelector = dynamic(() => import('@/components/admin/zoneSelector').then((mod) => mod.ZoneSelector), {
ssr: false
});
const LiveMap = dynamic(() => import('@/components/admin/mapPicker').then((mod) => mod.LiveMap), {
ssr: false
});
export default function AdminPage() {
const { useProtect } = useAdminConnexion();
const { gameState, changeState } = useAdmin();
useProtect();
return (
<div className='min-h-full bg-gray-200 p-10 flex flex-row flex-wrap 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 '>
<h2 className="text-2xl">Game state </h2>
<strong className="p-5 bg-gray-900 text-white text-xl rounded">Current : {gameState}</strong>
<RedButton onClick={() => changeState(GameState.SETUP)}>Reset game</RedButton>
<GreenButton onClick={() => changeState(GameState.PLACEMENT)}>Start placement</GreenButton>
<BlueButton onClick={() => changeState(GameState.PLAYING)}>Start game</BlueButton>
<div className='min-h-full bg-gray-200 p-10 flex flex-row content-start gap-5'>
<div className="h-full">
<div className='w-max h-1/2 gap-3 bg-white p-10 flex flex-col text-center shadow-2xl '>
<h2 className="text-2xl">Game state </h2>
<strong className="p-5 bg-gray-900 text-white text-xl rounded">Current : {gameState}</strong>
<RedButton onClick={() => changeState(GameState.SETUP)}>Reset game</RedButton>
<GreenButton onClick={() => changeState(GameState.PLACEMENT)}>Start placement</GreenButton>
<BlueButton onClick={() => changeState(GameState.PLAYING)}>Start game</BlueButton>
</div>
</div>
{gameState == GameState.PLACEMENT && <div className="max-h-5/6"><TeamReady /></div>}
{(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>
)
}

View File

@@ -2,8 +2,9 @@
import { useLocation } from "@/hook/useLocation";
import { useEffect, useState } from "react";
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 useAdmin from "@/hook/useAdmin";
function MapPan(props) {
const map = useMap();
@@ -91,4 +92,34 @@ export function ZonePicker({ minZone, setMinZone, maxZone, setMaxZone, editMode,
<MapEventListener onClick={handleClick} onMouseMove={handleMouseMove} />
</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='&copy; <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>
)
}

View File

@@ -2,7 +2,7 @@ import useAdmin from "@/hook/useAdmin"
export function TeamReady() {
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>
{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">

View File

@@ -37,7 +37,7 @@ export function ZoneSelector() {
}, [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>
{editMode == EditMode.MIN && <RedButton onClick={() => setEditMode(EditMode.MAX)}>Edit end zone</RedButton>}
{editMode == EditMode.MAX && <BlueButton onClick={() => setEditMode(EditMode.MIN)}>Edit start zone</BlueButton>}

View File

@@ -10,6 +10,8 @@ const adminContext = createContext();
function AdminProvider({ children }) {
const [teams, setTeams] = useState([]);
const [zoneSettings, setZoneSettings] = useState(null)
const [zone, setZone] = useState(null);
const [zoneExtremities, setZoneExtremities] = useState(null);
const { adminSocket } = useSocket();
const { loggedIn } = useAdminConnexion();
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
useSocketListener(adminSocket, "teams", setTeams);
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 (
<adminContext.Provider value={value}>
{children}

View File

@@ -2,7 +2,7 @@ import { useAdminContext } from "@/context/adminContext";
import { useSocket } from "@/context/socketContext";
export default function useAdmin(){
const {teams, gameState, zoneSettings } = useAdminContext();
const {teams, gameState, zoneSettings, zone, zoneExtremities } = useAdminContext();
const {adminSocket} = useSocket();
function pollTeams() {
@@ -42,6 +42,6 @@ export default function useAdmin(){
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 };
}