- {editMode == EditMode.MIN &&
setEditMode(EditMode.MAX)}>Click to edit first zone }
- {editMode == EditMode.MAX &&
setEditMode(EditMode.MIN)}>Click to edit last zone }
+ {localZoneSettings.type == ZoneTypes.CIRCLE && <>
+
+
+
+
-
-
Reduction number
-
-
modifyZoneSettings("reductionCount", customStringToInt(e.target.value))} />
+
+
+ {editMode == EditMode.MIN && setEditMode(EditMode.MAX)}>Click to edit first zone }
+ {editMode == EditMode.MAX && setEditMode(EditMode.MIN)}>Click to edit last zone }
+
+
+
Reduction number
+
+ updateReductionCount(customStringToInt(e.target.value))} />
+
+
+
+
Zone duration
+
+ updateDuration(customStringToInt(e.target.value))} />
+
+
+
+
Timeout
+
+ setLocalOutOfZoneDelay(customStringToInt(e.target.value))} />
+
+
+
+ Apply
-
-
Zone duration
-
- modifyZoneSettings("duration", customStringToInt(e.target.value))} />
-
-
-
-
Timeout
-
- setLocalOutOfZoneDelay(customStringToInt(e.target.value))} />
-
-
-
- Apply
-
-
+ >}
);
}
diff --git a/traque-front/app/admin/parameters/components/placementZoneSelector.jsx b/traque-front/app/admin/parameters/components/placementZoneSelector.jsx
new file mode 100644
index 0000000..e8e3a3a
--- /dev/null
+++ b/traque-front/app/admin/parameters/components/placementZoneSelector.jsx
@@ -0,0 +1,69 @@
+import { useEffect, useState } from "react";
+import { Circle, Tooltip } from "react-leaflet";
+import { List } from "@/components/list";
+import { CustomMapContainer, MapEventListener } from "@/components/map";
+import useAdmin from '@/hook/useAdmin';
+import useMultipleCircleDraw from "@/hook/useMultipleCircleDraw";
+
+function Drawings({ placementZones, addZone, removeZone, handleRightClick }) {
+ const { handleLeftClick, handleRightClick: handleRightClickDrawing } = useMultipleCircleDraw(placementZones, addZone, removeZone, 30);
+
+ function modifiedHandleRightClick(e) {
+ handleRightClickDrawing(e);
+ handleRightClick();
+ }
+
+ return (<>
+
+ { placementZones.map(placementZone =>
+
+ {placementZone.name}
+
+ )}
+ >);
+}
+
+export default function PlacementZoneSelector() {
+ const { teams, getTeam, placementTeam } = useAdmin();
+ const [selectedTeamId, setSelectedTeamId] = useState(null);
+ const [placementZones, setPlacementZones] = useState([]);
+
+ useEffect(() => {
+ setPlacementZones(teams.filter(team => team.startingArea).map(team => ({id: team.id, name: team.name, center: team.startingArea.center, radius: team.startingArea.radius})));
+ }, [teams]);
+
+ function addPlacementZone(center, radius) {
+ if (!selectedTeamId) return;
+ const placementZonesFiltered = placementZones.filter(placementZone => placementZone.id !== selectedTeamId);
+ const newZone = {id: selectedTeamId, name: getTeam(selectedTeamId).name, center: center, radius: radius};
+ placementTeam(selectedTeamId, newZone);
+ setPlacementZones([...placementZonesFiltered, newZone]);
+ }
+
+ function removePlacementZone(id) {
+ placementTeam(id, null);
+ setPlacementZones(placementZones.filter((placementZone) => placementZone.id !== id));
+ }
+
+ return (
+
+
+
+ setSelectedTeamId(null)} />
+
+
+
+
+
Teams
+
+
setSelectedTeamId(selectedTeamId != id ? id : null)}>
+ { (team) =>
+
+ }
+
+
+
+ );
+}
diff --git a/traque-front/app/admin/parameters/components/playingZoneSelector.jsx b/traque-front/app/admin/parameters/components/playingZoneSelector.jsx
new file mode 100644
index 0000000..55484a5
--- /dev/null
+++ b/traque-front/app/admin/parameters/components/playingZoneSelector.jsx
@@ -0,0 +1,40 @@
+import { useState } from "react";
+import dynamic from "next/dynamic";
+import { ZoneTypes } from "@/util/types";
+
+// Imported at runtime and not at compile time
+const CircleZoneSelector = dynamic(() => import('./circleZoneSelector'), { ssr: false });
+const PolygonZoneSelector = dynamic(() => import('./polygonZoneSelector'), { ssr: false });
+
+function ZoneTypeButton({title, onClick, isSelected}) {
+ const grayStyle = "bg-gray-300 hover:bg-gray-400";
+ const blueStyle = "bg-custom-light-blue";
+
+ return (
+
+ );
+}
+
+export default function PlayingZoneSelector() {
+ const [zoneType, setZoneType] = useState(ZoneTypes.POLYGON);
+
+ return (
+
+
+
Type de zone :
+
setZoneType(ZoneTypes.CIRCLE)} isSelected={zoneType == ZoneTypes.CIRCLE} />
+ setZoneType(ZoneTypes.POLYGON)} isSelected={zoneType == ZoneTypes.POLYGON} />
+
+
+ {zoneType == ZoneTypes.CIRCLE &&
+
+ }
+ {zoneType == ZoneTypes.POLYGON &&
+
+ }
+
+
+ );
+}
diff --git a/traque-front/app/admin/parameters/components/polygonZoneSelector.jsx b/traque-front/app/admin/parameters/components/polygonZoneSelector.jsx
index 4a6e51b..7ae4bed 100644
--- a/traque-front/app/admin/parameters/components/polygonZoneSelector.jsx
+++ b/traque-front/app/admin/parameters/components/polygonZoneSelector.jsx
@@ -7,15 +7,24 @@ import { CustomMapContainer, MapEventListener } from "@/components/map";
import { TextInput } from "@/components/input";
import { Node, LabeledPolygon } from "@/components/layer";
import useAdmin from "@/hook/useAdmin";
-import useMapPolygonDraw from "@/hook/useMapPolygonDraw";
+import useMapPolygonDraw from "@/hook/usePolygonDraw";
import useLocalVariable from "@/hook/useLocalVariable";
+import { defaultZoneSettings } from "@/util/configurations";
+import { ZoneTypes } from "@/util/types";
-function Drawings({ polygons, addPolygon, removePolygon }) {
- const { currentPolygon, highlightNodes, handleLeftClick, handleRightClick, handleMouseMove } = useMapPolygonDraw(polygons, addPolygon, removePolygon);
+function Drawings({ localZoneSettings, addZone, removeZone }) {
+ const [polygons, setPolygons] = useState([]);
+ const { currentPolygon, highlightNodes, handleLeftClick, handleRightClick, handleMouseMove } = useMapPolygonDraw(polygons, addZone, removeZone);
+
+ useEffect(() => {
+ if (localZoneSettings.type == ZoneTypes.POLYGON) {
+ setPolygons(localZoneSettings.polygons.map(zone => zone.polygon));
+ }
+ }, [localZoneSettings]);
return (<>
- {polygons.map((polygon, i) =>
)}
+ {localZoneSettings.polygons.map((zone, i) =>
)}
{ currentPolygon.length > 0 && <>
@@ -24,73 +33,87 @@ function Drawings({ polygons, addPolygon, removePolygon }) {
>);
}
-export default function PolygonZoneSelector({zoneSettings, modifyZoneSettings, applyZoneSettings}) {
+export default function PolygonZoneSelector() {
const defaultDuration = 10;
- const [polygons, setPolygons] = useState([]);
- const {outOfZoneDelay, updateSettings} = useAdmin();
+ const {zoneSettings, outOfZoneDelay, updateSettings} = useAdmin();
+ const [localZoneSettings, setLocalZoneSettings, applyLocalZoneSettings] = useLocalVariable(zoneSettings, (e) => updateSettings({zone: e}));
const [localOutOfZoneDelay, setLocalOutOfZoneDelay, applyLocalOutOfZoneDelay] = useLocalVariable(outOfZoneDelay, (e) => updateSettings({outOfZoneDelay: e}));
useEffect(() => {
- if (zoneSettings) {
- setPolygons(zoneSettings.polygons.map((zone) => zone.polygon));
+ if (localZoneSettings.type != ZoneTypes.POLYGON) {
+ setLocalZoneSettings(defaultZoneSettings.polygon);
}
- }, [zoneSettings]);
+ }, [localZoneSettings]);
- function idFromPolygon(polygon) {
- return (polygon[0].lat + polygon[1].lat + polygon[2].lat).toString() + (polygon[0].lng + polygon[1].lng + polygon[2].lng).toString();
+ function getNewPolygonName() {
+ const existingIds = new Set(localZoneSettings.polygons.map(zone => zone.id));
+ for (let i = 0; i < 26; i++) {
+ const letter = String.fromCharCode(65 + i);
+ if (!existingIds.has(letter)) {
+ return letter;
+ }
+ }
+ return "XXX";
}
- function addPolygon(polygon) {
- const newPolygons = [...zoneSettings.polygons, {id: idFromPolygon(polygon), polygon: polygon, duration: defaultDuration}];
- modifyZoneSettings("polygons", newPolygons);
+ function setLocalZoneSettingsPolygons(polygons) {
+ setLocalZoneSettings({type: localZoneSettings.type, polygons: polygons});
}
- function removePolygon(i) {
- const newPolygons = zoneSettings.polygons.filter((_, index) => index !== i);
- modifyZoneSettings("polygons", newPolygons);
+ function addZone(polygon) {
+ setLocalZoneSettingsPolygons([...localZoneSettings.polygons, {id: getNewPolygonName(), polygon: polygon, duration: defaultDuration}]);
}
- function updateDuration(i, duration) {
- const newPolygons = zoneSettings.polygons.map((zone, index) => index === i ? {id: zone.id, polygon: zone.polygon, duration: duration} : zone);
- modifyZoneSettings("polygons", newPolygons);
+ function removeZone(i) {
+ setLocalZoneSettingsPolygons(localZoneSettings.polygons.filter((_, index) => index !== i));
}
- function handleSettingsSubmit() {
- applyZoneSettings();
+ function updateDuration(id, duration) {
+ setLocalZoneSettingsPolygons(localZoneSettings.polygons.map(zone => zone.id === id ? {...zone, duration: duration} : zone));
+ }
+
+ function handleSubmit() {
+ applyLocalZoneSettings();
applyLocalOutOfZoneDelay();
}
+
+ function customStringToInt(e) {
+ return parseInt(e, 10) || null;
+ }
return (
-
-
-
-
-
-
-
-
Reduction order
+ {localZoneSettings.type == ZoneTypes.POLYGON && <>
+
+
+
+
-
modifyZoneSettings("polygons", polygons)}>
- { (zone, i) =>
-
-
Zone {i+1}
-
-
updateDuration(i, parseInt(e.target.value, 10))}/>
+
+
+
Reduction order
+
+
+ { (zone) =>
+
+
Zone {zone.id}
+
+ updateDuration(zone.id, customStringToInt(e.target.value))}/>
+
+ }
+
+
+
Timeout
+
+ setLocalOutOfZoneDelay(customStringToInt(e.target.value))}/>
- }
-
-
-
Timeout
-
- setLocalOutOfZoneDelay(parseInt(e.target.value, 10))} />
+
+
+ Apply
-
- Apply
-
-
+ >}
);
}
diff --git a/traque-front/app/admin/parameters/components/teamManager.jsx b/traque-front/app/admin/parameters/components/teamManager.jsx
index bddd933..470f330 100644
--- a/traque-front/app/admin/parameters/components/teamManager.jsx
+++ b/traque-front/app/admin/parameters/components/teamManager.jsx
@@ -1,31 +1,61 @@
+import { useState } from "react";
import { ReorderList } from '@/components/list';
import useAdmin from '@/hook/useAdmin';
+import useLocalVariable from "@/hook/useLocalVariable";
+import { TextInput } from "@/components/input";
+import { Section } from "@/components/section";
function TeamManagerItem({ team }) {
const { captureTeam, removeTeam } = useAdmin();
return (
-
-
-
{team.name}
-
-
{String(team.id).padStart(6, '0').replace(/(\d{3})(\d{3})/, "$1 $2")}
-
captureTeam(team.id)} />
-
removeTeam(team.id)} />
-
+
+
{team.name}
+
+
{String(team.id).padStart(6, '0').replace(/(\d{3})(\d{3})/, "$1 $2")}
+
captureTeam(team.id)} />
+
removeTeam(team.id)} />
);
}
export default function TeamManager() {
- const { teams, reorderTeams } = useAdmin();
+ const { teams, addTeam, reorderTeams, sendPositionDelay, updateSettings } = useAdmin();
+ const [teamName, setTeamName] = useState('');
+ const [localSendPositionDelay, setLocalSendPositionDelay, applyLocalSendPositionDelay] = useLocalVariable(sendPositionDelay, (e) => updateSettings({sendPositionDelay: e}));
+
+ function handleTeamSubmit(e) {
+ e.preventDefault();
+ if (teamName !== "") {
+ addTeam(teamName);
+ setTeamName("")
+ }
+ }
return (
-
reorderTeams(teams.map(team => team.id))}>
- {(team) => (
-
- )}
-
+
+
+
+ reorderTeams(teams.map(team => team.id))}>
+ {(team) => (
+
+ )}
+
+
+
+
Interval between position updates
+
+ setLocalSendPositionDelay(parseInt(e.target.value, 10))} onBlur={applyLocalSendPositionDelay} />
+
+
+
);
}
diff --git a/traque-front/app/admin/parameters/page.js b/traque-front/app/admin/parameters/page.js
index b221eb7..f5dc215 100644
--- a/traque-front/app/admin/parameters/page.js
+++ b/traque-front/app/admin/parameters/page.js
@@ -2,87 +2,66 @@
import { useState } from "react";
import dynamic from "next/dynamic";
import Link from "next/link";
-import { TextInput } from "@/components/input";
-import { Section } from "@/components/section";
-import { CustomButton } from "@/components/button";
import { useAdminConnexion } from "@/context/adminConnexionContext";
-import useAdmin from '@/hook/useAdmin';
import Messages from "./components/messages";
import TeamManager from './components/teamManager';
-import useLocalVariable from "@/hook/useLocalVariable";
-import { ZoneTypes } from "@/util/types";
-import { defaultZoneSettings } from "@/util/configurations";
+import PlayingZoneSelector from "./components/playingZoneSelector";
// Imported at runtime and not at compile time
-const CircleZoneSelector = dynamic(() => import('./components/circleZoneSelector'), { ssr: false });
-const PolygonZoneSelector = dynamic(() => import('./components/polygonZoneSelector'), { ssr: false });
+const PlacementZoneSelector = dynamic(() => import('./components/placementZoneSelector'), { ssr: false });
+
+const Tabs = {
+ PLACEMENT_ZONES: "placement_zones",
+ PLAYING_ZONES: "playing_zones",
+}
+
+function ParametersTitle() {
+ return (
+
+
+
+
+
Paramètres
+
+ );
+}
+
+function TabButton({title, onClick, isSelected}) {
+ const grayStyle = "bg-gray-300 hover:bg-gray-400";
+ const blueStyle = "bg-custom-light-blue";
+
+ return (
+
+ );
+}
export default function ConfigurationPage() {
const { useProtect } = useAdminConnexion();
- const {zoneSettings, sendPositionDelay, updateSettings, addTeam} = useAdmin();
- const [teamName, setTeamName] = useState('');
- const [localZoneSettings, setLocalZoneSettings, applyLocalZoneSettings] = useLocalVariable(zoneSettings, (e) => updateSettings({zone: e}));
- const [localSendPositionDelay, setLocalSendPositionDelay, applyLocalSendPositionDelay] = useLocalVariable(sendPositionDelay, (e) => updateSettings({sendPositionDelay: e}));
+ const [currentTab, setCurrentTab] = useState(Tabs.PLACEMENT_ZONES);
useProtect();
- function modifyLocalZoneSettings(key, value) {
- setLocalZoneSettings(prev => ({...prev, [key]: value}));
- }
-
- function handleChangeZoneType() {
- setLocalZoneSettings(localZoneSettings.type == ZoneTypes.CIRCLE ? defaultZoneSettings.polygon : defaultZoneSettings.circle)
- }
-
- function handleTeamSubmit(e) {
- e.preventDefault();
- if (teamName !== "") {
- addTeam(teamName);
- setTeamName("")
- }
- }
-
return (
-
-
-
-
-
Paramètres
-
+
-
-
-
-
-
-
-
Interval between position updates
-
- setLocalSendPositionDelay(parseInt(e.target.value, 10))} onBlur={applyLocalSendPositionDelay} />
-
-
-
+
-
-
- {localZoneSettings &&
Change zone type }
+
+
+ setCurrentTab(Tabs.PLACEMENT_ZONES)} isSelected={currentTab == Tabs.PLACEMENT_ZONES}/>
+ setCurrentTab(Tabs.PLAYING_ZONES)} isSelected={currentTab == Tabs.PLAYING_ZONES}/>
-
- {localZoneSettings && localZoneSettings.type == ZoneTypes.CIRCLE &&
-
- }
- {localZoneSettings && localZoneSettings.type == ZoneTypes.POLYGON &&
-
- }
+
+ { currentTab == Tabs.PLAYING_ZONES &&
+
+ }
+ { currentTab == Tabs.PLACEMENT_ZONES &&
+
+ }
diff --git a/traque-front/components/layer.jsx b/traque-front/components/layer.jsx
index f8197db..a874341 100644
--- a/traque-front/components/layer.jsx
+++ b/traque-front/components/layer.jsx
@@ -9,7 +9,7 @@ export function Node({pos, nodeSize = 5, color = 'black'}) {
);
}
-export function LabeledPolygon({polygon, number, color = 'black', opacity = '0.5', border = 3, iconSize = 24, iconColor = 'white'}) {
+export function LabeledPolygon({polygon, label, color = 'black', opacity = '0.5', border = 3, iconSize = 24, iconColor = 'white'}) {
const length = polygon.length;
if (length < 3) return null;
@@ -26,7 +26,7 @@ export function LabeledPolygon({polygon, number, color = 'black', opacity = '0.5
// Idea : take the mean point of the largest connected subpolygon
const meanPoint = {lat: sum.lat / length, lng: sum.lng / length}
- const numberIcon = L.divIcon({
+ const labelIcon = L.divIcon({
html: `
${number}
`,
- className: 'custom-number-icon',
+ ">${label}
`,
+ className: 'custom-label-icon',
iconSize: [iconSize, iconSize],
iconAnchor: [iconSize / 2, iconSize / 2]
});
return (<>
-
+
>);
}
diff --git a/traque-front/components/list.jsx b/traque-front/components/list.jsx
index b8216cf..38cf43d 100644
--- a/traque-front/components/list.jsx
+++ b/traque-front/components/list.jsx
@@ -1,13 +1,19 @@
import { useEffect, useState } from 'react';
import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd';
-export function List({array, children}) {
+export function List({array, selectedId, onSelected, children}) {
+ const canSelect = selectedId !== undefined
+ const cursor = () => canSelect ? " cursor-pointer" : "";
+ const outline = (id) => canSelect && id === selectedId ? " outline outline-4 outline-black" : "";
+
return (