diff --git a/client/.env b/client/.env new file mode 100644 index 0000000..ef94cee --- /dev/null +++ b/client/.env @@ -0,0 +1 @@ +VITE_SCOUT_VERSION=LAR.0 \ No newline at end of file diff --git a/client/src/RootMenu.tsx b/client/src/RootMenu.tsx index 3f8a233..df9c7e1 100644 --- a/client/src/RootMenu.tsx +++ b/client/src/RootMenu.tsx @@ -7,6 +7,7 @@ function RootMenu() {

Vitruvian Scouting

+
Version {import.meta.env.VITE_SCOUT_VERSION}
Match diff --git a/client/src/apps/match/MatchApp.tsx b/client/src/apps/match/MatchApp.tsx index b861985..62e2e76 100644 --- a/client/src/apps/match/MatchApp.tsx +++ b/client/src/apps/match/MatchApp.tsx @@ -17,25 +17,18 @@ import { usePreventUnload } from '../../lib/usePreventUnload'; const schedule = scheduleFile as MatchSchedule -type countKeys = keyof MatchScores; - interface MatchScores { autoShootNear: number; autoShootMid: number; autoShootFar: number; autoAmp: number; autoMiss: number; - autoPreload: number; - autoPickup: number; hold: number; // Did the robot hold a note between auto and teleop? 0=no, 1=yes teleShootNear: number; teleShootMid: number; teleShootFar: number; teleAmp: number; teleMiss: number; - telePickupSpeaker: number; - telePickupMiddle: number; - telePickupSource: number; trap: number; } const defualtScores: MatchScores = { @@ -44,17 +37,12 @@ const defualtScores: MatchScores = { autoShootFar: 0, autoAmp: 0, autoMiss: 0, - autoPreload: 0, - autoPickup: 0, hold: 0, teleShootNear: 0, teleShootMid: 0, teleShootFar: 0, teleAmp: 0, teleMiss: 0, - telePickupSpeaker: 0, - telePickupMiddle: 0, - telePickupSource: 0, trap: 0, }; @@ -69,7 +57,6 @@ function MatchApp() { const [climbPosition, setClimbPosition] = useState('none'); const [showCheck, setShowCheck] = useState(false); const [scouterName, setScouterName] = useState(''); - const [robotPosition, setRobotPosition] = useState(); const [scouterPosition, setScouterPosition] = useState(); @@ -78,6 +65,54 @@ function MatchApp() { ['blue_1', 'blue_2', 'blue_3'] as (string | undefined)[] ).includes(robotPosition); + const handleAbsentRobot = async () => { + if ( + robotPosition == undefined || + matchNumber == undefined + ) { + alert('Check if your signed in, and you have the match number') + return; } + + const data: MatchData = { + metadata: { + scouterName, + robotPosition, + matchNumber, + robotTeam: undefined + }, + leftStartingZone: leave, + autoNotes: { + near: count.autoShootNear, + mid: count.autoShootMid, + far: count.autoShootFar, + amp: count.autoAmp, + miss: count.autoMiss + }, + teleNotes: { + near: count.teleShootNear, + mid: count.teleShootMid, + far: count.teleShootFar, + amp: count.teleAmp, + miss: count.autoMiss + }, + trapNotes: count.trap, + climb: climbPosition, + }; + + sendQueue('/data/match', data); + setCount(defualtScores); + setClimbPosition('none'); + setLeave(false); + setMatchNumber(matchNumber + 1); + setCountHistory([]); + + setShowCheck(true); + + setTimeout(() => { + setShowCheck(false); + }, 3000); + } + const handleSubmit = async () => { if ( robotPosition == undefined || @@ -129,6 +164,15 @@ function MatchApp() { }; + const showConfirmationDialog = () => { + if (window.confirm('Are you sure you want to mark as absent?')) { + // User confirmed, call the action + handleAbsentRobot(); + // Optionally, you can also scroll to the top + scrollTo(0, 0); + } + }; + const undoCount = () => { if (countHistory.length > 0) { setCountHistory(prevHistory => prevHistory.slice(0, -1)); @@ -139,9 +183,6 @@ function MatchApp() { setCountHistory([...countHistory, count]); setCount(newCount); }; - const handleCount = (key: countKeys) => { - handleSetCount({ ...count, [key]: count[key] + 1 }); - }; useEffect(() => { setTeamNumber( @@ -217,6 +258,13 @@ function MatchApp() {

Team Number

+
+ +
+

Autonomous

Endgame

-
- +
+
diff --git a/client/src/apps/pit/PitApp.tsx b/client/src/apps/pit/PitApp.tsx index a8c1120..f406aee 100644 --- a/client/src/apps/pit/PitApp.tsx +++ b/client/src/apps/pit/PitApp.tsx @@ -1,9 +1,8 @@ import MultiButton from '../../components/MultiButton'; //import ToggleButton from '../../components/ToggleButton' -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import Checkbox from '../../components/Checkbox'; import { PitFile, teamRoles, drivebase } from 'requests'; -import { postJson } from '../../lib/postJson'; import LinkButton from '../../components/LinkButton'; import { MaterialSymbol } from 'react-material-symbols'; import TeamDropdown from '../../components/TeamDropdown'; @@ -12,6 +11,8 @@ import SignIn from '../../components/SignIn'; import ConeStacker from '../../components/ConeStacker'; import { usePreventUnload } from '../../lib/usePreventUnload'; import ImageUploader from '../../components/ImageUploader'; +import { useFetchJson } from '../../lib/useFetch'; +import { postJson } from '../../lib/postJson'; function PitApp() { @@ -32,6 +33,7 @@ function PitApp() { setAutoInputValues([...autoInputValues, '']); }; + const [scoutedTeams, refreshScoutedTeams] = useFetchJson('/data/pit/scouted-teams'); const [autoInputValues, setAutoInputValues] = useState(['']); const [role, setRole] = useState(); @@ -52,6 +54,10 @@ function PitApp() { const [scouterName, setScouterName] = useState(''); const [robotImage, setRobotImage] = useState(''); + useEffect(() => { + const timeout = setInterval(refreshScoutedTeams, 60 * 1000); + return () => clearInterval(timeout); + }, [refreshScoutedTeams]); const handleSubmit = async() => { if (!drivetrain || !role){ @@ -86,6 +92,7 @@ function PitApp() { try { const result = await postJson('/data/pit', data); if (!result.ok) throw new Error('Request Did Not Succeed'); + refreshScoutedTeams(); setAutoInputValues(['']); setAmpChecked(false); setAmpPrefChecked(false); @@ -165,7 +172,7 @@ function PitApp() {

Team Number

- +
diff --git a/client/src/components/EndGameButton.tsx b/client/src/components/EndGameButton.tsx index 938987c..8c4c296 100644 --- a/client/src/components/EndGameButton.tsx +++ b/client/src/components/EndGameButton.tsx @@ -1,17 +1,22 @@ import { Dispatch, SetStateAction } from 'react'; +import { MatchScores } from '../apps/match/MatchApp'; import { ClimbPosition, ScouterPosition } from 'requests'; import MultiButton from './MultiButton'; function EndgameButton({ setClimb, + setCount, climbPosition, alliance, scouterPosition, + count, }: { setClimb: Dispatch>; + setCount: Dispatch>; climbPosition: ClimbPosition; alliance: boolean | undefined; scouterPosition: ScouterPosition | undefined; + count: MatchScores; }) { // const [alliance, setAlliance] = useState(false); //false=red, true=blue, null=hollow purple @@ -19,6 +24,13 @@ function EndgameButton({ setClimb(newClimb); }; + const handleTrap = () => { + setCount(prevCount => ({ + ...prevCount, + ['trap']: prevCount['trap'] + 1, + })); + } + return ( <>
+
+ +
); } diff --git a/client/src/components/FieldButton.tsx b/client/src/components/FieldButton.tsx index c8e1538..e4fa6ee 100644 --- a/client/src/components/FieldButton.tsx +++ b/client/src/components/FieldButton.tsx @@ -1,7 +1,6 @@ -import { Dispatch, SetStateAction, useState } from 'react'; +import { Dispatch, SetStateAction } from 'react'; import { MatchScores } from '../apps/match/MatchApp'; -import Checkbox from './Checkbox'; -import { PickupLocation, ScouterPosition } from 'requests'; +import { ScouterPosition } from 'requests'; import MultiButton from './MultiButton'; type countKeys = keyof MatchScores; @@ -62,193 +61,47 @@ function FieldButton({ alliance: boolean | undefined; scouterPosition: ScouterPosition | undefined; }) { - const [pickupLocation, setPickupLocation] = useState< - PickupLocation | undefined - >(); - const heldFromAuto = - count.hold && - !( - count.teleShootNear || - count.teleShootMid || - count.teleShootFar || - count.teleAmp || - count.teleMiss - ); const handleCount = (autoKey: countKeys, teleKey: countKeys) => { - if (pickupLocation) { + if (teleOp || !count.hold){ const finalKey = teleOp ? teleKey : autoKey; setCount(prevCount => ({ ...prevCount, [finalKey]: prevCount[finalKey] + 1, })); - const pickupKeys = { - preload: 'autoPreload', - pickup: 'autoPickup', - speaker: 'telePickupSpeaker', - middle: 'telePickupMiddle', - source: 'telePickupSource', - } as const; - setCount(prevCount => ({ - ...prevCount, - [pickupKeys[pickupLocation]]: - prevCount[pickupKeys[pickupLocation]] + 1, - })); - } else if (heldFromAuto && teleOp) { - const finalKey = teleOp ? teleKey : autoKey; - setCount(prevCount => ({ - ...prevCount, - [finalKey]: prevCount[finalKey] + 1, - })); - } else if ( - (count.autoPreload || count.autoPickup) && - !count.hold && - !teleOp - ) { - const finalKey = teleOp ? teleKey : autoKey; - const finalPickupLocation = - pickupLocation == 'autoPreload' ? 'autoPreload' : 'autoPickup'; - setCount(prevCount => ({ - ...prevCount, - [finalKey]: prevCount[finalKey] + 1, - [finalPickupLocation]: prevCount[finalPickupLocation] + 1, - })); } - setPickupLocation(undefined); }; const handleLeave = () => { setLeave?.(!leave); }; + const fieldColors = alliance ? ['bg-blue-300/70', 'bg-blue-500/70', 'bg-blue-700/70'] : ['bg-red-200/70', 'bg-red-400/70', 'bg-red-600/70']; + return ( <> -
+
{!teleOp && ( - - {' '} - Did the robot leave? - - )} -
- - {/* - - red on right, blue alliance - red is source, blue is speaker - red on right, red alliance - red is speaker, blue is source - blue on right, blue alliance - red is source, blue is speaker - blue on right, red alliance - red is speaker, blue is source - - */} - -
- {teleOp ? ( - !heldFromAuto ? ( - scouterPosition === 'red_right' ? ( - alliance ? ( - - ) : ( - - ) - ) : alliance ? ( - - ) : ( - - ) - ) : ( -
- Note held from auto + <> +
+

Mobility?

+

The robot must cross the gray
line to earn mobility.

- ) - ) : count.autoPreload || count.autoPickup ? ( -
- ) : ( - + + )}
+ ${!teleOp && count.hold ? 'grayscale' : ''}`}> {alliance ? ( <> @@ -267,7 +120,7 @@ function FieldButton({ handleCount={handleCount} autoKey='autoShootMid' teleKey='teleShootMid' - className='absolute left-[30%] top-[25%] z-10 h-[130%] w-[130%] overflow-hidden rounded-full bg-blue-400/70 text-left ' + className={`absolute left-[30%] top-[25%] z-10 h-[130%] w-[130%] overflow-hidden rounded-full text-left ${fieldColors[1]}`} textClassName='top-[3.5em] left-[3.5em] absolute' scouterPosition={scouterPosition} /> @@ -277,7 +130,7 @@ function FieldButton({ handleCount={handleCount} autoKey='autoShootFar' teleKey='teleShootFar' - className='absolute bottom-0 right-0 z-0 h-full w-full bg-red-400/70 text-left ' + className={`absolute bottom-0 right-0 z-0 h-full w-full bg-green-200/70 text-left ${fieldColors[0]}`} textClassName='top-[3.5em] left-[3em] absolute' scouterPosition={scouterPosition} /> @@ -290,7 +143,7 @@ function FieldButton({ handleCount={handleCount} autoKey='autoShootFar' teleKey='teleShootFar' - className='bottom-0 right-0 z-0 h-full w-full bg-red-400/70 p-[2.5em] text-right ' + className={`bottom-0 right-0 z-0 h-full w-full p-[2.5em] text-right ${fieldColors[0]}`} textClassName='top-[3.5em] right-[3em] absolute' scouterPosition={scouterPosition} /> @@ -300,7 +153,7 @@ function FieldButton({ handleCount={handleCount} autoKey='autoShootMid' teleKey='teleShootMid' - className='right-[30%] top-[25%] z-10 h-[130%] w-[130%] overflow-hidden rounded-full bg-blue-400/70 text-right ' + className={`right-[30%] top-[25%] z-10 h-[130%] w-[130%] overflow-hidden rounded-full text-right ${fieldColors[1]}`} textClassName='top-[3.25em] right-[3.5em] absolute' scouterPosition={scouterPosition} /> @@ -310,7 +163,7 @@ function FieldButton({ handleCount={handleCount} autoKey='autoShootNear' teleKey='teleShootNear' - className='bottom-[40px] left-[-120px] z-20 h-2/5 w-2/5 rounded-full bg-green-400/70 text-right' + className={`bottom-[40px] left-[-120px] z-20 h-2/5 w-2/5 rounded-full text-right ${fieldColors[2]}`} textClassName='top-[2.2em] right-[1.5em] absolute' scouterPosition={scouterPosition} /> @@ -319,9 +172,9 @@ function FieldButton({
- {count.hold === 0 || teleOp ? ( + className={`flex w-[40em] flex-row gap-2 py-2 transition-[filter] duration-200 + ${!teleOp && count.hold ? 'grayscale' : ''}`}> + {( <> )} - ) : ( -
- Held: 1 -
)}
diff --git a/client/src/components/TeamDropdown.tsx b/client/src/components/TeamDropdown.tsx index 738823b..fc15052 100644 --- a/client/src/components/TeamDropdown.tsx +++ b/client/src/components/TeamDropdown.tsx @@ -14,13 +14,17 @@ console.log(teamOptions); function TeamDropdown({ value, onChange, + disabledOptions, }: { value?: number | undefined; onChange?: Dispatch; + disabledOptions?: number[]; }) { + const options = disabledOptions ? teamOptions.map(e => ({...e, disabled: disabledOptions.includes(parseInt(e.value as string))})) : teamOptions; + return (
onChange?.(parseInt(value as string))} search diff --git a/client/src/index.css b/client/src/index.css index 8c5cfdc..b3ecc12 100644 --- a/client/src/index.css +++ b/client/src/index.css @@ -10,6 +10,10 @@ html { @apply snap-y; } + + body { + @apply overscroll-none; + } } @layer utilities { diff --git a/requests/index.d.ts b/requests/index.d.ts index c4b2dcf..2e43b3f 100644 --- a/requests/index.d.ts +++ b/requests/index.d.ts @@ -81,7 +81,7 @@ export interface SuperDataAggregations{ export interface MetaData { scouterName: string; matchNumber: number; - robotTeam: number; + robotTeam?: number; robotPosition: RobotPosition } diff --git a/server/src/server.ts b/server/src/server.ts index 37e7cc5..4aea747 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -76,6 +76,10 @@ app.get('/data/retrieve/super', async (req, res) => { res.send(await superAverageAndMax()); }) +app.get('/data/pit/scouted-teams', async (req, res) => { + res.send((await pitApp.find({}, {teamNumber: 1})).map(e => e.teamNumber)); +}) + app.get('/image/:teamId.jpeg', async (req, res) => { const { teamId } = req.params; console.log(teamId);