diff --git a/android/app/release/output-metadata.json b/android/app/release/output-metadata.json new file mode 100644 index 0000000..d711d94 --- /dev/null +++ b/android/app/release/output-metadata.json @@ -0,0 +1,20 @@ +{ + "version": 3, + "artifactType": { + "type": "APK", + "kind": "Directory" + }, + "applicationId": "com.mashkif.app", + "variantName": "release", + "elements": [ + { + "type": "SINGLE", + "filters": [], + "attributes": [], + "versionCode": 1, + "versionName": "1.0", + "outputFile": "app-release.apk" + } + ], + "elementType": "File" +} \ No newline at end of file diff --git a/electron/package.json b/electron/package.json index ce9c8d4..1a7bb4a 100644 --- a/electron/package.json +++ b/electron/package.json @@ -1,6 +1,6 @@ { "name": "MAshkif", - "version": "0.1.1", + "version": "0.2.5", "description": "General purpose scouting app for all applications with easy to use interface and data management", "author": { "name": "Asaf Meizner", diff --git a/electron/src/index.ts b/electron/src/index.ts index 3bd1952..dbd9563 100644 --- a/electron/src/index.ts +++ b/electron/src/index.ts @@ -26,7 +26,8 @@ const capacitorFileConfig: CapacitorElectronConfig = getCapacitorElectronConfig( // Initialize our app. You can pass menu templates into the app here. // const myCapacitorApp = new ElectronCapacitorApp(capacitorFileConfig); -const myCapacitorApp = new ElectronCapacitorApp(capacitorFileConfig, trayMenuTemplate, appMenuBarMenuTemplate); +// const myCapacitorApp = new ElectronCapacitorApp(capacitorFileConfig, trayMenuTemplate, appMenuBarMenuTemplate); +const myCapacitorApp = new ElectronCapacitorApp(capacitorFileConfig); // If deeplinking is enabled then we will set it up here. if (capacitorFileConfig.electron?.deepLinkingEnabled) { diff --git a/electron/src/setup.ts b/electron/src/setup.ts index e7c957b..5775e04 100644 --- a/electron/src/setup.ts +++ b/electron/src/setup.ts @@ -52,8 +52,8 @@ export class ElectronCapacitorApp { new MenuItem({ label: 'Quit App', role: 'quit' }), ]; private AppMenuBarMenuTemplate: (MenuItem | MenuItemConstructorOptions)[] = [ - { role: process.platform === 'darwin' ? 'appMenu' : 'fileMenu' }, - { role: 'viewMenu' }, + // { role: process.platform === 'darwin' ? 'appMenu' : 'fileMenu' }, + // { role: 'viewMenu' }, ]; private mainWindowState; private loadWebApp; @@ -224,10 +224,10 @@ export function setupContentSecurityPolicy(customScheme: string): void { ...details.responseHeaders, 'Content-Security-Policy': [ electronIsDev - ? `default-src ${customScheme}://* 'unsafe-inline' devtools://* 'unsafe-eval' data:` - : `default-src ${customScheme}://* 'unsafe-inline' data:`, + ? `default-src ${customScheme}://* 'unsafe-inline' devtools://* 'unsafe-eval' data:; connect-src *` + : `default-src ${customScheme}://* 'unsafe-inline' data:; connect-src *`, ], }, }); }); -} +} \ No newline at end of file diff --git a/src/components/chart-configs/specificTeam/configs.js b/src/components/chart-configs/specificTeam/configs.js index a814f10..7bb252d 100644 --- a/src/components/chart-configs/specificTeam/configs.js +++ b/src/components/chart-configs/specificTeam/configs.js @@ -277,6 +277,20 @@ export const autoPathPerRoundConfig = (scoutingData, teamNumber) => ({ stacked: true, name: 'Midline 1 (Amp Edge)', }, + { + key: 'Path9', + label: 'Path 9', + color: '#82b5d8', + stacked: true, + name: 'Other', + }, + { + key: 'Path10', + label: 'Path 10', + color: '#c4162a', + stacked: true, + name: "Didn't Move", + } ], chartSettings: { showGridlines: true, @@ -286,6 +300,7 @@ export const autoPathPerRoundConfig = (scoutingData, teamNumber) => ({ yAxisLabel: 'Autonomous Path Taken', yAxisMin: 0, yAxisMax: 1, + xAxisLabel: 'Round Number', xAxisLabelRotation: 0, showTooltip: true, tooltipSettings: { @@ -303,6 +318,57 @@ export const autoPathPerRoundConfig = (scoutingData, teamNumber) => ({ sortOrder: 'ascending', }); +export const actualVsExpectedConfig = (scoutingData, teamNumber) => ({ + data: specificTeamFunctions.actualVsExpectedData(scoutingData, teamNumber), + title: `Actual vs. Expected Autonomous Scores for Team ${teamNumber}`, + scoringTypes: [ + { + key: 'actualScore', + label: 'Actual Score', + color: '#73bf69', + name: 'Actual Score', + }, + { + key: 'expectedScore', + label: 'Expected Score', + color: '#ff8042', + name: 'Expected Score', + }, + { + key: 'deviation', + label: 'Deviation', + color: '#ff4444', + name: 'Score Deviation', + }, + ], + chartSettings: { + showGridlines: true, + gridlineColor: '#444444', + }, + xKey: 'roundNumber', + yAxisLabel: 'Score', + yAxisMin: 'auto', + yAxisMax: 'auto', + xAxisLabel: 'Round Number', + xAxisLabelRotation: 0, + showTooltip: true, + tooltipSettings: { + backgroundColor: '#333333', + borderRadius: '8px', + fontSize: '0.875rem', + cursorColor: 'rgba(255, 255, 255, 0.1)', + }, + showLegend: true, + interactiveLegend: true, + legendPosition: 'top', + responsive: true, + maintainAspectRatio: true, + showDataLabels: true, + dataLabelPosition: 'inside', + dataLabelRotation: 0, + sortOrder: 'ascending', +}); + export const autoPathUsagePieConfig = (scoutingData, teamNumber) => ({ data: specificTeamFunctions.autoPathPieData(scoutingData, teamNumber), title: `Autonomous Path Usage Percentages for Team ${teamNumber}`, @@ -355,6 +421,18 @@ export const autoPathUsagePieConfig = (scoutingData, teamNumber) => ({ color: '#d0ed57', name: 'Midline 1 (Amp Edge)', }, + { + key: 'Path9', + label: 'Path 9', + color: '#82b5d8', + name: 'Other', + }, + { + key: 'Path10', + label: 'Path 10', + color: '#c4162a', + name: "Didn't Move", + } ], chartSettings: { showTooltip: true, @@ -371,7 +449,8 @@ export const autoPathUsagePieConfig = (scoutingData, teamNumber) => ({ maintainAspectRatio: true, colors: [ '#8884d8', '#82ca9d', '#ff4444', '#ffc658', - '#ff8042', '#73bf69', '#a4de6c', '#d0ed57' + '#ff8042', '#73bf69', '#a4de6c', '#d0ed57', + '#82b5d8', '#c4162a' ], showLabels: true, labelPosition: 'outside', @@ -792,6 +871,7 @@ export const endgameClimbDataPerRoundConfig = (scoutingData, teamNumber) => ({ yAxisMin: 0, yAxisMax: 1, xAxisLabelRotation: 0, + xAxisLabel: 'Round Number', showTooltip: true, tooltipSettings: { backgroundColor: '#333333', @@ -901,6 +981,55 @@ export const trapPerRoundConfig = (scoutingData, teamNumber) => ({ dataLabelRotation: 0, }); +export const endgameTrapUsagePieConfig = (scoutingData, teamNumber) => ({ + data: specificTeamFunctions.endgameTrapPieData(scoutingData, teamNumber), + title: `Endgame Trap Score for Team ${teamNumber}`, + scoringTypes: [ + { + key: '0', + label: 'Note 0', + color: '#8884d8', + name: '0 Note', + }, + { + key: '1', + label: 'Note 1', + color: '#82ca9d', + name: '1 Note', + }, + { + key: '2', + label: 'Note 2', + color: '#ff4444', + name: '2 Note', + }, + { + key: '3', + label: 'Note 3', + color: '#ffc658', + name: '3 Note', + }, + ], + chartSettings: { + showTooltip: true, + tooltipSettings: { + backgroundColor: '#333333', + borderRadius: '8px', + fontSize: '0.875rem', + textColor: '#ffffff', + cursorColor: 'rgba(255, 255, 255, 0.1)', + }, + showLegend: true, + legendPosition: 'top', + legendLayout: 'horizontal', + maintainAspectRatio: true, + colors: ['#8884d8', '#82ca9d', '#ff4444', '#ffc658'], + showLabels: true, + labelPosition: 'outside', + }, + responsive: true, +}); + export const teamPerformanceRadarConfig = (scoutingData, teamNumber) => { // Calculate the max values across all teams for each category const maxSpeakerTeleOp = allTeamsFunctions.speakerTeleOpMax(scoutingData); // Max for Teleop Speaker Score diff --git a/src/components/chart-configs/specificTeam/functions.js b/src/components/chart-configs/specificTeam/functions.js index a93062b..8497e23 100644 --- a/src/components/chart-configs/specificTeam/functions.js +++ b/src/components/chart-configs/specificTeam/functions.js @@ -149,6 +149,8 @@ export function autoPathStackedData(scoutingData, teamNumber) { Path6: entry.a_gp_Path === "6" ? 1 : 0, Path7: entry.a_gp_Path === "7" ? 1 : 0, Path8: entry.a_gp_Path === "8" ? 1 : 0, + Path9: entry.a_gp_Path === "9" ? 1 : 0, + Path10: entry.a_gp_Path === "10" ? 1 : 0, }; }); @@ -173,6 +175,8 @@ export function autoPathPieData(scoutingData, teamNumber) { Path6: 0, Path7: 0, Path8: 0, + Path9: 0, + Path10: 0, }; teamData.forEach((entry) => { @@ -184,6 +188,8 @@ export function autoPathPieData(scoutingData, teamNumber) { pathCounts.Path6 += entry.a_gp_Path === "6" ? 1 : 0; pathCounts.Path7 += entry.a_gp_Path === "7" ? 1 : 0; pathCounts.Path8 += entry.a_gp_Path === "8" ? 1 : 0; + pathCounts.Path9 += entry.a_gp_Path === "9" ? 1 : 0; + pathCounts.Path10 += entry.a_gp_Path === "10" ? 1 : 0; }); return Object.keys(pathCounts).map((pathKey) => ({ @@ -533,6 +539,33 @@ export function endgameTrapPerRound(scoutingData, teamNumber) { return trapData; } +export function endgameTrapPieData(scoutingData, teamNumber) { + const teamData = scoutingData.filter((entry) => entry.teamNumber === teamNumber); + + if (!teamData.length) { + return []; + } + + const trapCounts = { + '0': 0, + '1': 0, + '2': 0, + '3': 0, + }; + + teamData.forEach((entry) => { + const trapScore = entry.cn || 0; // assuming cn represents the trap score + if (trapScore >= 0 && trapScore <= 3) { + trapCounts[trapScore] += 1; + } + }); + + return Object.keys(trapCounts).map((trapKey) => ({ + name: `${trapKey} Note`, + value: trapCounts[trapKey], + })); +} + export function speakerAutoAverage(scoutingData, teamNumber) { const teamData = scoutingData.filter((entry) => entry.teamNumber === teamNumber); @@ -638,4 +671,39 @@ export function maxEndPositionForMatch(scoutingData, teamNumber) { }, 0); return maxScore; +} + + +export function actualVsExpectedData(scoutingData, teamNumber) { + const expectedScoresByPath = { + Path1: 3, // Speaker 3 (Source Side) + Path2: 2, // Speaker 2 (Middle) + Path3: 1, // Speaker 1 (Amp Side) + Path4: 5, // Midline 5 (Source Edge) + Path5: 4, // Midline 4 + Path6: 3, // Midline 3 (Middle) + Path7: 2, // Midline 2 + Path8: 1, // Midline 1 (Amp Edge) + Path9: 1, // Other + Path10: 0, // Didn't Move + }; + const teamData = scoutingData.filter((entry) => entry.teamNumber === teamNumber); + + if (!teamData.length) { return []; } + + return teamData.map((entry) => { + const roundNumber = entry.matchNumber; + const actualScore = entry.ausc || 0; // Autonomous Speaker Score + + // Find the path taken and expected score + let pathTaken = Object.keys(expectedScoresByPath).find(path => entry.a_gp_Path === path.slice(4)); + let expectedScore = expectedScoresByPath[pathTaken] || 0; + + return { + roundNumber, + actualScore, + expectedScore, + deviation: actualScore - expectedScore // Calculate the deviation + }; + }); } \ No newline at end of file diff --git a/src/components/charts/MultiNumberDisplay.js b/src/components/charts/MultiNumberDisplay.js index c8176df..9632c15 100644 --- a/src/components/charts/MultiNumberDisplay.js +++ b/src/components/charts/MultiNumberDisplay.js @@ -3,7 +3,7 @@ import { ResponsiveContainer } from 'recharts'; const MultiNumberDisplay = ({ config }) => { const defaultConfig = { - mainTitle: '', // New main title in config + mainTitle: '', values: [ { color: '#00acc1', @@ -60,6 +60,9 @@ const MultiNumberDisplay = ({ config }) => { +

+ {`${percentage.toFixed(2)}%`} +

); })} diff --git a/src/components/charts/barChart.js b/src/components/charts/barChart.js index 503bb84..78f409c 100644 --- a/src/components/charts/barChart.js +++ b/src/components/charts/barChart.js @@ -18,12 +18,15 @@ const CustomBarShape = (props) => { gradient, } = props; + const adjustedY = height < 0 ? y + height : y; + const adjustedHeight = Math.abs(height); + return ( div { @@ -155,4 +249,29 @@ height: 100%; max-height: 100%; } + + .team-selection { + flex: 1 1 90vw; + max-width: 90vw; + background-color: #1e1e1e; + border-radius: 8px; + padding: 10px; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + } + + .team-selection-container { + flex: 1 1 90vw; + max-width: 90vw; + background-color: #1e1e1e; + border-radius: 8px; + padding: 10px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.5); + height: 100%; + display: flex; + justify-content: center; + align-items: center; + } } diff --git a/src/pages/Visualization.js b/src/pages/Visualization.js index d0117b7..8aa6e02 100644 --- a/src/pages/Visualization.js +++ b/src/pages/Visualization.js @@ -17,6 +17,7 @@ const Visualization = () => { const [teamNumber, setTeamNumber] = useState(5951); const [team1Number, setTeam1Number] = useState(5951); const [team2Number, setTeam2Number] = useState(5951); + const [selectedTeams, setSelectedTeams] = useState([]); const scoutingData = getScoutingData(); useEffect(() => { @@ -48,6 +49,28 @@ const Visualization = () => { const isSectionOpen = (sectionId) => openSections.includes(sectionId); + const teamNumbers = [...new Set(scoutingData.map(entry => entry.teamNumber))]; + + const handleTeamSelection = (teamNumber) => { + setSelectedTeams(prevSelected => + prevSelected.includes(teamNumber) + ? prevSelected.filter(num => num !== teamNumber) + : [...prevSelected, teamNumber] + ); + }; + + const selectAllTeams = () => { + setSelectedTeams(teamNumbers); + }; + + const deselectAllTeams = () => { + setSelectedTeams([]); + }; + + const filteredScoutingData = scoutingData.filter(entry => + selectedTeams.length === 0 || selectedTeams.includes(entry.teamNumber) + ); + const allTeamsRenderGeneralSection = () => (

toggleSection('general')}> @@ -56,10 +79,10 @@ const Visualization = () => { {isSectionOpen('general') && (
- +
- +
)} @@ -74,22 +97,22 @@ const Visualization = () => { {isSectionOpen('autonomous') && (
- +
- +
- +
- +
- +
- +
)} @@ -104,28 +127,28 @@ const Visualization = () => { {isSectionOpen('teleop') && (
- +
- +
- +
- +
- +
- +
- +
- +
)} @@ -140,10 +163,10 @@ const Visualization = () => { {isSectionOpen('endgame') && (
- +
- +
)} @@ -197,6 +220,9 @@ const Visualization = () => {
+
+ +
@@ -254,6 +280,9 @@ const Visualization = () => {
+
+ +

)} @@ -325,6 +354,12 @@ const Visualization = () => {
+
+ +
+
+ +
)} @@ -404,6 +439,25 @@ const Visualization = () => { const renderAllTeams = () => ( <> +
+
+ + +
+ +
+ {teamNumbers.map(teamNumber => ( + + ))} +
+
{allTeamsRenderGeneralSection()} {allTeamsRenderAutonomousSection()} {allTeamsRenderTeleopSection()} diff --git a/src/quizData.json b/src/quizData.json index 217434f..6a64b5a 100644 --- a/src/quizData.json +++ b/src/quizData.json @@ -85,7 +85,9 @@ "5": "קו אמצע 4", "6": "קו אמצע 3 (אמצע)", "7": "קו אמצע 2", - "8": "קו אמצע 1 (קצה אמפ)" + "8": "קו אמצע 1 (קצה אמפ)", + "9": "אחר", + "10": "לא נסה" }, "defaultValue": 1 },