From 4bfb13daee7159bf85a098f7648c4de1c00af313 Mon Sep 17 00:00:00 2001 From: YoniKiriaty Date: Sun, 19 Apr 2026 14:36:23 +0300 Subject: [PATCH 1/5] Yoni - stop crashing the metrics when bps is null --- .../frontend/src/strategy/components/MetricsChart.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/scouting/frontend/src/strategy/components/MetricsChart.tsx b/apps/scouting/frontend/src/strategy/components/MetricsChart.tsx index 1d01dfc4..ca557a1a 100644 --- a/apps/scouting/frontend/src/strategy/components/MetricsChart.tsx +++ b/apps/scouting/frontend/src/strategy/components/MetricsChart.tsx @@ -6,7 +6,7 @@ import type { TeamData } from "@repo/scouting_types"; const NUMBER_OF_DIGITS = 2; const Metric: FC<{ name: string; - value: number; + value?: number; colors: string; onClick?: () => void; }> = ({ name, value, colors, onClick }) => ( @@ -19,7 +19,7 @@ const Metric: FC<{ {name} - {value.toFixed(NUMBER_OF_DIGITS)} + {value?.toFixed(NUMBER_OF_DIGITS)} ); From e7184513ca66a8ec486175b34b767046e32ead08 Mon Sep 17 00:00:00 2001 From: karnishein <214869392+karnishein@users.noreply.github.com> Date: Mon, 20 Apr 2026 10:30:32 +0300 Subject: [PATCH 2/5] karni- added types and backend --- apps/scouting/backend/src/routes/index.ts | 2 + .../backend/src/routes/pit-scout-router.ts | 39 +++++++++++++++++++ packages/scouting_types/pit_scout/index.ts | 19 +++++++++ 3 files changed, 60 insertions(+) create mode 100644 apps/scouting/backend/src/routes/pit-scout-router.ts create mode 100644 packages/scouting_types/pit_scout/index.ts diff --git a/apps/scouting/backend/src/routes/index.ts b/apps/scouting/backend/src/routes/index.ts index 8e0417c1..d702acc6 100644 --- a/apps/scouting/backend/src/routes/index.ts +++ b/apps/scouting/backend/src/routes/index.ts @@ -13,6 +13,7 @@ import { tinderRouter } from "./tinder-router"; import { bpsRouter } from "./bps-router"; import { superScoutRouter } from "./super-scout-router"; import { picklistRouter } from "./picklist-router"; +import { pitScoutRouter } from "./pit-scout-router"; export const apiRouter = Router(); @@ -28,6 +29,7 @@ apiRouter.use("/tinder", tinderRouter); apiRouter.use("/bps", bpsRouter); apiRouter.use("/super", superScoutRouter); apiRouter.use("/picklist", picklistRouter); +apiRouter.use("/pit", pitScoutRouter); apiRouter.get("/health", (req, res) => { res.status(StatusCodes.OK).send({ message: "Healthy!" }); diff --git a/apps/scouting/backend/src/routes/pit-scout-router.ts b/apps/scouting/backend/src/routes/pit-scout-router.ts new file mode 100644 index 00000000..0162846d --- /dev/null +++ b/apps/scouting/backend/src/routes/pit-scout-router.ts @@ -0,0 +1,39 @@ +//בס"ד + +import { Router } from "express"; +import { flow, pipe } from "fp-ts/lib/function"; +import { getDb } from "../middleware/db"; +import { bind, bindTo, fold, fromEither, map } from "fp-ts/lib/TaskEither"; +import { PitScout, pitScoutCodec } from "@repo/scouting_types/pit_scout"; +import { createBodyVerificationPipe, foldResponse } from "@repo/flow-utils"; +import { right as rightEither } from "fp-ts/lib/Either"; +import { StatusCodes } from "http-status-codes"; +import { getSuperCollection, superScoutRouter } from "./super-scout-router"; +import { mongofyQuery } from "@repo/flow-utils"; + +export const pitScoutRouter = Router(); + +export const getPitCollection = flow( + getDb, + map((db) => db.collection("pit")), +); + +pitScoutRouter.post("/", async (req, res) => { + await pipe( + rightEither(req), + createBodyVerificationPipe(pitScoutCodec), + fromEither, + bindTo("pitScout"), + bind("collection", getPitCollection), + map(({ pitScout, collection }) => collection.insertOne(pitScout)), + foldResponse(res), + )(); +}); + +pitScoutRouter.get("/", async (req, res) => { + await flow( + getPitCollection, + map((collection) => collection.find(mongofyQuery(req.query)).toArray()), + foldResponse(res), + )(); +}); diff --git a/packages/scouting_types/pit_scout/index.ts b/packages/scouting_types/pit_scout/index.ts new file mode 100644 index 00000000..ca0fd783 --- /dev/null +++ b/packages/scouting_types/pit_scout/index.ts @@ -0,0 +1,19 @@ +//בס"ד + +import * as t from "io-ts"; + +const pitScoutAnswerCodec = t.union([t.undefined, t.string]); + +const pitScoutBooleanCodec = t.union([t.boolean, t.undefined]); + +export const pitScoutCodec = t.type({ + teamNumber: t.number, + robotWeight: pitScoutAnswerCodec, + ballCapacity: pitScoutAnswerCodec, + hasTurret: pitScoutBooleanCodec, + canPassTrench: pitScoutBooleanCodec, + canPassBumpEasily: pitScoutBooleanCodec, + extraInfo: pitScoutAnswerCodec, +}); + +export type PitScout = t.TypeOf; From caafe754ddc410bc0e727bdb79ef78e8f325d16a Mon Sep 17 00:00:00 2001 From: karnishein <214869392+karnishein@users.noreply.github.com> Date: Mon, 20 Apr 2026 17:38:38 +0300 Subject: [PATCH 3/5] karni- continued --- apps/scouting/backend/src/routes/pit-scout-router.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/scouting/backend/src/routes/pit-scout-router.ts b/apps/scouting/backend/src/routes/pit-scout-router.ts index 0162846d..db3ad581 100644 --- a/apps/scouting/backend/src/routes/pit-scout-router.ts +++ b/apps/scouting/backend/src/routes/pit-scout-router.ts @@ -3,12 +3,10 @@ import { Router } from "express"; import { flow, pipe } from "fp-ts/lib/function"; import { getDb } from "../middleware/db"; -import { bind, bindTo, fold, fromEither, map } from "fp-ts/lib/TaskEither"; +import { bind, bindTo, fromEither, map } from "fp-ts/lib/TaskEither"; import { PitScout, pitScoutCodec } from "@repo/scouting_types/pit_scout"; import { createBodyVerificationPipe, foldResponse } from "@repo/flow-utils"; import { right as rightEither } from "fp-ts/lib/Either"; -import { StatusCodes } from "http-status-codes"; -import { getSuperCollection, superScoutRouter } from "./super-scout-router"; import { mongofyQuery } from "@repo/flow-utils"; export const pitScoutRouter = Router(); From 5a81a6088be52508d8461599e5878489a2bcb54a Mon Sep 17 00:00:00 2001 From: karnishein <214869392+karnishein@users.noreply.github.com> Date: Mon, 20 Apr 2026 21:27:26 +0300 Subject: [PATCH 4/5] karni- added the whole pit scouting submition --- apps/scouting/backend/build.ts | 2 +- .../backend/src/routes/pit-scout-router.ts | 2 +- apps/scouting/frontend/src/App.tsx | 2 + .../strategy/tabs/pit-scout/PitScoutTab.tsx | 205 ++++++++++++++++++ packages/scouting_types/pit_scout/index.ts | 19 -- packages/scouting_types/rebuilt/index.ts | 1 + .../scouting_types/rebuilt/pit_scout/index.ts | 22 ++ 7 files changed, 232 insertions(+), 21 deletions(-) create mode 100644 apps/scouting/frontend/src/strategy/tabs/pit-scout/PitScoutTab.tsx delete mode 100644 packages/scouting_types/pit_scout/index.ts create mode 100644 packages/scouting_types/rebuilt/pit_scout/index.ts diff --git a/apps/scouting/backend/build.ts b/apps/scouting/backend/build.ts index 0f4439ef..3890f2a2 100644 --- a/apps/scouting/backend/build.ts +++ b/apps/scouting/backend/build.ts @@ -2,7 +2,7 @@ import { build, context } from "esbuild"; import { spawn } from "child_process"; -const isDev = process.env.NODE_ENV === "DEV"; +const isDev = process.env.NODE_ENV !== "DEV"; const bundlePath = "dist/bundle.js"; diff --git a/apps/scouting/backend/src/routes/pit-scout-router.ts b/apps/scouting/backend/src/routes/pit-scout-router.ts index db3ad581..2f11925f 100644 --- a/apps/scouting/backend/src/routes/pit-scout-router.ts +++ b/apps/scouting/backend/src/routes/pit-scout-router.ts @@ -4,10 +4,10 @@ import { Router } from "express"; import { flow, pipe } from "fp-ts/lib/function"; import { getDb } from "../middleware/db"; import { bind, bindTo, fromEither, map } from "fp-ts/lib/TaskEither"; -import { PitScout, pitScoutCodec } from "@repo/scouting_types/pit_scout"; import { createBodyVerificationPipe, foldResponse } from "@repo/flow-utils"; import { right as rightEither } from "fp-ts/lib/Either"; import { mongofyQuery } from "@repo/flow-utils"; +import { PitScout, pitScoutCodec } from "@repo/scouting_types"; export const pitScoutRouter = Router(); diff --git a/apps/scouting/frontend/src/App.tsx b/apps/scouting/frontend/src/App.tsx index bd144668..150af986 100644 --- a/apps/scouting/frontend/src/App.tsx +++ b/apps/scouting/frontend/src/App.tsx @@ -13,6 +13,7 @@ import { CURRENT_COMPETITION } from "@repo/scouting_types"; import { StrategyNavigationBar } from "./strategy/components/StrategyNavBar"; import { SuperScoutTab } from "./strategy/tabs/super-scout/SuperScoutTab"; import { Tinder } from "./strategy/tabs/Tinder"; +import { PitScoutTab } from "./strategy/tabs/pit-scout/PitScoutTab"; const App: FC = () => { return ( @@ -30,6 +31,7 @@ const App: FC = () => { } /> } /> } /> + } /> } /> diff --git a/apps/scouting/frontend/src/strategy/tabs/pit-scout/PitScoutTab.tsx b/apps/scouting/frontend/src/strategy/tabs/pit-scout/PitScoutTab.tsx new file mode 100644 index 00000000..f0f6bc18 --- /dev/null +++ b/apps/scouting/frontend/src/strategy/tabs/pit-scout/PitScoutTab.tsx @@ -0,0 +1,205 @@ +//בס"ד + +import { useState, type FC } from "react"; +import type { PitScout, pitScoutBoolean } from "@repo/scouting_types"; + +type BooleanFieldKey = "hasTurret" | "canPassTrench" | "canPassBumpEasily"; +type NumberFieldKey = "robotWeight" | "ballCapacity"; + +const NUMBER_FIELDS: { + key: NumberFieldKey; + label: string; + placeholder: string; +}[] = [ + { + key: "robotWeight", + label: "Robot Weight (lbs)", + placeholder: "e.g. 120", + }, + { key: "ballCapacity", label: "Ball Capacity", placeholder: "e.g. 50" }, +]; + +const PIT_SCOUT_URL = "/api/v1/pit/"; + +const BOOLEAN_FIELDS: { key: BooleanFieldKey; label: string }[] = [ + { key: "hasTurret", label: "Has turret?" }, + { key: "canPassTrench", label: "Can pass trench?" }, + { key: "canPassBumpEasily", label: "Can pass bump easily?" }, +]; + +const initialState: PitScout = { + teamNumber: 0, + robotWeight: undefined, + ballCapacity: undefined, + hasTurret: undefined, + canPassTrench: undefined, + canPassBumpEasily: undefined, + extraInfo: undefined, +}; + +export const PitScoutTab: FC = () => { + const [form, setForm] = useState(initialState); + const [status, setStatus] = useState< + "idle" | "loading" | "success" | "error" + >("idle"); + const [errorMsg, setErrorMsg] = useState(""); + + const setString = (key: keyof PitScout, value: string) => + setForm((form) => ({ ...form, [key]: value || undefined })); + + const setBool = (key: BooleanFieldKey, value: pitScoutBoolean) => + setForm((form) => ({ + ...form, + [key]: form[key] === value ? undefined : value, + })); + + const handleSubmit = async () => { + if (!form.teamNumber) { + setStatus("error"); + setErrorMsg("Team number is required."); + return; + } + + setStatus("loading"); + try { + const res = await fetch(PIT_SCOUT_URL, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(form), + }); + + if (res.ok) { + setStatus("success"); + setForm(initialState); + } else { + const text = await res.text(); + setStatus("error"); + setErrorMsg(text || "Submission failed."); + } + } catch (error) { + setStatus("error"); + setErrorMsg(error instanceof Error ? error.message : "Network error."); + } + }; + + return ( +
+
+

+ Team Identification +

+
+ + + setForm((form) => ({ + ...form, + teamNumber: parseInt(event.target.value) || 0, + })) + } + placeholder="0000" + /> +
+
+ +
+ {NUMBER_FIELDS.map(({ key, label, placeholder }) => ( +
+ + setString(key, event.target.value)} + placeholder={placeholder} + /> +
+ ))} +
+ +
+

+ Mechanical Capabilities +

+
+ {BOOLEAN_FIELDS.map(({ key, label }) => ( +
+ + {label} + +
+ + +
+
+ ))} +
+
+ +
+

+ extra information +

+