From 01b67ee9770444497f97adf70ee98a30c8b0d5d1 Mon Sep 17 00:00:00 2001 From: YoniKiriaty Date: Sat, 14 Feb 2026 20:02:24 +0200 Subject: [PATCH 1/5] Yoni - first version --- apps/scouting/frontend/src/App.tsx | 4 +- .../src/strategy/components/MatchForecast.tsx | 199 ++++++++++++++++++ 2 files changed, 202 insertions(+), 1 deletion(-) create mode 100644 apps/scouting/frontend/src/strategy/components/MatchForecast.tsx diff --git a/apps/scouting/frontend/src/App.tsx b/apps/scouting/frontend/src/App.tsx index eb3ceeb..573040e 100644 --- a/apps/scouting/frontend/src/App.tsx +++ b/apps/scouting/frontend/src/App.tsx @@ -5,14 +5,16 @@ import { Route, Routes } from "react-router-dom"; import { ScoutedMatches } from "./scouter/pages/ScoutedMatches"; import { TeamTab } from "./strategy/components/TeamTab"; import { GeneralDataTable } from "./scouter/components/GeneralDataTable"; +import { MatchForecast } from "./strategy/components/MatchForecast"; const App: FC = () => { return ( } /> - } /> + } /> } /> } /> + } /> ); }; diff --git a/apps/scouting/frontend/src/strategy/components/MatchForecast.tsx b/apps/scouting/frontend/src/strategy/components/MatchForecast.tsx new file mode 100644 index 0000000..b953ff6 --- /dev/null +++ b/apps/scouting/frontend/src/strategy/components/MatchForecast.tsx @@ -0,0 +1,199 @@ +// בס"ד +import { useEffect, useState, type FC, type JSX } from "react"; +import type { Forecast } from "@repo/scouting_types"; +import { Fuel, Zap } from "lucide-react"; + +interface Alliances { + redAlliance: [number, number, number]; + blueAlliance: [number, number, number]; +} + +interface StatRowProps { + label: string; + icon: JSX.Element; + red: number; + blue: number; + subtext: string; +} +const StatRow: FC = ({ label, icon, red, blue, subtext }) => { + const isRedLeading = red > blue; + return ( +
+
+
+ {red} +
+
+
+ {icon}{" "} + + {label} + +
+
+ {subtext} +
+
+
+ {blue} +
+
+ {/* Background visual bar */} +
+
+
+ ); +}; + +interface MiniStatProps { + label: string; + value: number; + align: "left" | "right"; +} + +const MiniStat: FC = ({ label, value, align = "left" }) => ( +
+ + {label} + + {value} +
+); + +const querifyAlliances = (alliances: Alliances) => { + const params = new URLSearchParams(); + alliances.redAlliance.forEach((team) => { + params.append("redAlliance", team.toString()); + }); + alliances.blueAlliance.forEach((team) => { + params.append("blueAlliance", team.toString()); + }); + return params; +}; + +export const MatchForecast: FC = () => { + const [forecast, setForecast] = useState(); + const [alliances, setAlliances] = useState({ + redAlliance: [4590, 1690, 1577], + blueAlliance: [2230, 2231, 4586], + }); + + const updateForecast = async () => { + if (!alliances) { + return; + } + + const query = querifyAlliances(alliances); + try { + const response = await fetch(`/api/v1/forecast?${query.toString()}`); + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`Bad Response: ${errorText}`); + } + const fetchedForecast: Forecast = await response.json(); + setForecast(fetchedForecast); + } catch (error: unknown) { + alert(error); + } + }; + useEffect(() => { + void updateForecast(); + }, [alliances]); + + return ( +
+
+
+ + Red Alliance + +
+
+
+ Prediction +
+ VS +
+
+ + Blue Alliance + +
+
+ + {/* Stats Grid */} + {forecast && ( +
+ {/* Fuel Section */} + } + red={forecast.allianceData.redAlliance.fuel.fullGame} + blue={forecast.allianceData.blueAlliance.fuel.fullGame} + subtext="Full Game Total" + /> + + {/* Detailed Breakdown */} +
+ {/* Red Detailed Card */} +
+

+ Red Breakdown +

+ + +
+ +
+ + {/* Blue Detailed Card */} +
+

+ Blue Breakdown +

+ + +
+ +
+
+
+ )} +
+ ); +}; + +/* --- Sub-Components for Cleanliness --- */ From 60f5d94dd5a23577113f998f986499352f9d6a91 Mon Sep 17 00:00:00 2001 From: YoniKiriaty Date: Sat, 14 Feb 2026 20:04:04 +0200 Subject: [PATCH 2/5] Yoni - removed linting stuff --- .../src/strategy/components/MatchForecast.tsx | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/apps/scouting/frontend/src/strategy/components/MatchForecast.tsx b/apps/scouting/frontend/src/strategy/components/MatchForecast.tsx index b953ff6..a60b074 100644 --- a/apps/scouting/frontend/src/strategy/components/MatchForecast.tsx +++ b/apps/scouting/frontend/src/strategy/components/MatchForecast.tsx @@ -15,6 +15,8 @@ interface StatRowProps { blue: number; subtext: string; } +const PERCENT_BASE = 100; + const StatRow: FC = ({ label, icon, red, blue, subtext }) => { const isRedLeading = red > blue; return ( @@ -45,11 +47,11 @@ const StatRow: FC = ({ label, icon, red, blue, subtext }) => { {/* Background visual bar */}
); @@ -57,8 +59,8 @@ const StatRow: FC = ({ label, icon, red, blue, subtext }) => { interface MiniStatProps { label: string; - value: number; - align: "left" | "right"; + value: string | number; + align?: "left" | "right"; } const MiniStat: FC = ({ label, value, align = "left" }) => ( @@ -85,10 +87,11 @@ const querifyAlliances = (alliances: Alliances) => { export const MatchForecast: FC = () => { const [forecast, setForecast] = useState(); - const [alliances, setAlliances] = useState({ - redAlliance: [4590, 1690, 1577], - blueAlliance: [2230, 2231, 4586], - }); + const [alliances, setAlliances] = useState(); + // { + // redAlliance: [4590, 1690, 1577], + // blueAlliance: [2230, 2231, 4586], + // } const updateForecast = async () => { if (!alliances) { From 88fbca61043cf5a40eb1710fe282b7004d314d33 Mon Sep 17 00:00:00 2001 From: YoniKiriaty Date: Wed, 18 Feb 2026 16:49:35 +0200 Subject: [PATCH 3/5] Yoni - got basic sections good --- .../src/strategy/components/MatchForecast.tsx | 185 +++++++++--------- 1 file changed, 94 insertions(+), 91 deletions(-) diff --git a/apps/scouting/frontend/src/strategy/components/MatchForecast.tsx b/apps/scouting/frontend/src/strategy/components/MatchForecast.tsx index a60b074..aa81831 100644 --- a/apps/scouting/frontend/src/strategy/components/MatchForecast.tsx +++ b/apps/scouting/frontend/src/strategy/components/MatchForecast.tsx @@ -1,7 +1,6 @@ // בס"ד -import { useEffect, useState, type FC, type JSX } from "react"; +import { useEffect, useState, type FC } from "react"; import type { Forecast } from "@repo/scouting_types"; -import { Fuel, Zap } from "lucide-react"; interface Alliances { redAlliance: [number, number, number]; @@ -9,68 +8,65 @@ interface Alliances { } interface StatRowProps { - label: string; - icon: JSX.Element; red: number; blue: number; - subtext: string; } -const PERCENT_BASE = 100; -const StatRow: FC = ({ label, icon, red, blue, subtext }) => { +const StatRow: FC = ({ red, blue }) => { const isRedLeading = red > blue; return ( -
+
{red}
-
-
- {icon}{" "} - - {label} - -
-
- {subtext} -
+
+ {`${isRedLeading ? "Red" : "Blue"} Wins`}
{blue}
{/* Background visual bar */} -
-
); }; - -interface MiniStatProps { +interface CenterStatProps { label: string; - value: string | number; - align?: "left" | "right"; + red: number; + blue: number; } -const MiniStat: FC = ({ label, value, align = "left" }) => ( -
- - {label} - - {value} +const DECIMAL_DIGIT_AMOUNT = 0; + +const CenterStat: FC = ({ label, red, blue }) => ( +
+ {/* Red Value */} +
+ + {red.toFixed(DECIMAL_DIGIT_AMOUNT)} + +
+ + {/* Center Label Pill */} +
+ + {label} + +
+ + {/* Blue Value */} +
+ + {blue.toFixed(DECIMAL_DIGIT_AMOUNT)} + +
); @@ -85,13 +81,15 @@ const querifyAlliances = (alliances: Alliances) => { return params; }; +const testMatch: Alliances | undefined = undefined; +// { +// redAlliance: [4590, 1690, 1577], +// blueAlliance: [2230, 2231, 4586], +// }; + export const MatchForecast: FC = () => { const [forecast, setForecast] = useState(); - const [alliances, setAlliances] = useState(); - // { - // redAlliance: [4590, 1690, 1577], - // blueAlliance: [2230, 2231, 4586], - // } + const [alliances, setAlliances] = useState(testMatch); const updateForecast = async () => { if (!alliances) { @@ -115,12 +113,15 @@ export const MatchForecast: FC = () => { void updateForecast(); }, [alliances]); + const redData = forecast?.allianceData.redAlliance; + const blueData = forecast?.allianceData.blueAlliance; + return (
- Red Alliance + {alliances?.redAlliance.join(", ")}
@@ -131,65 +132,67 @@ export const MatchForecast: FC = () => {
- Blue Alliance + {alliances?.blueAlliance.join(", ")}
{/* Stats Grid */} - {forecast && ( + {redData && blueData && (
{/* Fuel Section */} - } - red={forecast.allianceData.redAlliance.fuel.fullGame} - blue={forecast.allianceData.blueAlliance.fuel.fullGame} - subtext="Full Game Total" - /> + {/* Detailed Breakdown */} -
- {/* Red Detailed Card */} -
-

- Red Breakdown +
+
+
+

+ Details

- - -
- +
- {/* Blue Detailed Card */} -
-

- Blue Breakdown -

- +
+
+
+ Auto +
+
+
+ - + +
+
+
+ Tele +
+
+
+ + -
-
From ee42bb1ec20a2c41da3364afa5e4be1d33b5023a Mon Sep 17 00:00:00 2001 From: YoniKiriaty Date: Wed, 18 Feb 2026 19:50:16 +0200 Subject: [PATCH 4/5] Yoni - removed test --- .../frontend/src/strategy/components/MatchForecast.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/scouting/frontend/src/strategy/components/MatchForecast.tsx b/apps/scouting/frontend/src/strategy/components/MatchForecast.tsx index aa81831..c3a4919 100644 --- a/apps/scouting/frontend/src/strategy/components/MatchForecast.tsx +++ b/apps/scouting/frontend/src/strategy/components/MatchForecast.tsx @@ -81,15 +81,14 @@ const querifyAlliances = (alliances: Alliances) => { return params; }; -const testMatch: Alliances | undefined = undefined; -// { +// const testMatch: Alliances | undefined = { // redAlliance: [4590, 1690, 1577], // blueAlliance: [2230, 2231, 4586], // }; export const MatchForecast: FC = () => { const [forecast, setForecast] = useState(); - const [alliances, setAlliances] = useState(testMatch); + const [alliances, setAlliances] = useState(); const updateForecast = async () => { if (!alliances) { From a8aeb144a5441848ad18b3f19e58de6465740323 Mon Sep 17 00:00:00 2001 From: YoniKiriaty Date: Wed, 18 Feb 2026 20:26:31 +0200 Subject: [PATCH 5/5] Yoni - split to files --- apps/scouting/frontend/src/App.tsx | 2 +- .../components/forecast/CenterStat.tsx | 40 ++++++++++ .../{ => forecast}/MatchForecast.tsx | 74 +------------------ .../strategy/components/forecast/StatRow.tsx | 33 +++++++++ 4 files changed, 77 insertions(+), 72 deletions(-) create mode 100644 apps/scouting/frontend/src/strategy/components/forecast/CenterStat.tsx rename apps/scouting/frontend/src/strategy/components/{ => forecast}/MatchForecast.tsx (69%) create mode 100644 apps/scouting/frontend/src/strategy/components/forecast/StatRow.tsx diff --git a/apps/scouting/frontend/src/App.tsx b/apps/scouting/frontend/src/App.tsx index eeb7b36..59ae0d0 100644 --- a/apps/scouting/frontend/src/App.tsx +++ b/apps/scouting/frontend/src/App.tsx @@ -4,7 +4,7 @@ import { ScoutMatch } from "./scouter/pages/ScoutMatch"; import { Route, Routes } from "react-router-dom"; import { ScoutedMatches } from "./scouter/pages/ScoutedMatches"; import { TeamTab } from "./strategy/components/TeamTab"; -import { MatchForecast } from "./strategy/components/MatchForecast"; +import { MatchForecast } from "./strategy/components/forecast/MatchForecast"; import { GeneralDataTable } from "./strategy/GeneralDataTable"; import { CompareTwo } from "./strategy/CompareTwo"; diff --git a/apps/scouting/frontend/src/strategy/components/forecast/CenterStat.tsx b/apps/scouting/frontend/src/strategy/components/forecast/CenterStat.tsx new file mode 100644 index 0000000..d8c24ac --- /dev/null +++ b/apps/scouting/frontend/src/strategy/components/forecast/CenterStat.tsx @@ -0,0 +1,40 @@ +// בס"ד + +import type { FC } from "react"; + +interface CenterStatProps { + label: string; + red: number; + blue: number; +} + +const DECIMAL_DIGIT_AMOUNT = 0; + +export const CenterStat: FC = ({ + label, + red, + blue, +}) => ( +
+ {/* Red Value */} +
+ + {red.toFixed(DECIMAL_DIGIT_AMOUNT)} + +
+ + {/* Center Label Pill */} +
+ + {label} + +
+ + {/* Blue Value */} +
+ + {blue.toFixed(DECIMAL_DIGIT_AMOUNT)} + +
+
+); diff --git a/apps/scouting/frontend/src/strategy/components/MatchForecast.tsx b/apps/scouting/frontend/src/strategy/components/forecast/MatchForecast.tsx similarity index 69% rename from apps/scouting/frontend/src/strategy/components/MatchForecast.tsx rename to apps/scouting/frontend/src/strategy/components/forecast/MatchForecast.tsx index c3a4919..a7eee5d 100644 --- a/apps/scouting/frontend/src/strategy/components/MatchForecast.tsx +++ b/apps/scouting/frontend/src/strategy/components/forecast/MatchForecast.tsx @@ -1,75 +1,14 @@ // בס"ד import { useEffect, useState, type FC } from "react"; import type { Forecast } from "@repo/scouting_types"; +import { CenterStat } from "./CenterStat"; +import { StatRow } from "./StatRow"; interface Alliances { redAlliance: [number, number, number]; blueAlliance: [number, number, number]; } -interface StatRowProps { - red: number; - blue: number; -} - -const StatRow: FC = ({ red, blue }) => { - const isRedLeading = red > blue; - return ( -
-
-
- {red} -
-
- {`${isRedLeading ? "Red" : "Blue"} Wins`} -
-
- {blue} -
-
- {/* Background visual bar */} -
- ); -}; -interface CenterStatProps { - label: string; - red: number; - blue: number; -} - -const DECIMAL_DIGIT_AMOUNT = 0; - -const CenterStat: FC = ({ label, red, blue }) => ( -
- {/* Red Value */} -
- - {red.toFixed(DECIMAL_DIGIT_AMOUNT)} - -
- - {/* Center Label Pill */} -
- - {label} - -
- - {/* Blue Value */} -
- - {blue.toFixed(DECIMAL_DIGIT_AMOUNT)} - -
-
-); - const querifyAlliances = (alliances: Alliances) => { const params = new URLSearchParams(); alliances.redAlliance.forEach((team) => { @@ -81,14 +20,9 @@ const querifyAlliances = (alliances: Alliances) => { return params; }; -// const testMatch: Alliances | undefined = { -// redAlliance: [4590, 1690, 1577], -// blueAlliance: [2230, 2231, 4586], -// }; - export const MatchForecast: FC = () => { const [forecast, setForecast] = useState(); - const [alliances, setAlliances] = useState(); + const [alliances, _setAlliances] = useState(); const updateForecast = async () => { if (!alliances) { @@ -200,5 +134,3 @@ export const MatchForecast: FC = () => {
); }; - -/* --- Sub-Components for Cleanliness --- */ diff --git a/apps/scouting/frontend/src/strategy/components/forecast/StatRow.tsx b/apps/scouting/frontend/src/strategy/components/forecast/StatRow.tsx new file mode 100644 index 0000000..674c035 --- /dev/null +++ b/apps/scouting/frontend/src/strategy/components/forecast/StatRow.tsx @@ -0,0 +1,33 @@ +// בס"ד + +import type { FC } from "react"; + +interface StatRowProps { + red: number; + blue: number; +} + +export const StatRow: FC = ({ red, blue }) => { + const isRedLeading = red > blue; + return ( +
+
+
+ {red} +
+
+ {`${isRedLeading ? "Red" : "Blue"} Wins`} +
+
+ {blue} +
+
+
+ ); +};