diff --git a/Utility.js b/Utility.js index 8ce2ad81..860dd2ef 100644 --- a/Utility.js +++ b/Utility.js @@ -34,11 +34,11 @@ export function numTrunc(value, pad = false) { } } -export function getUnique(array, field, drills) { +export function getUnique(array, drills) { const unique = []; drills.forEach((drillInfo) => { const idx = array.findIndex((item) => item["did"] === drillInfo["did"]); - if (idx >= 0) unique.push(drills[idx]); + if (idx >= 0) unique.push(drillInfo); }); return unique; } diff --git a/app/content/profile/index.js b/app/content/profile/index.js index b1fbebc9..f8bb55dc 100644 --- a/app/content/profile/index.js +++ b/app/content/profile/index.js @@ -21,17 +21,10 @@ import { TouchableOpacity, View, } from "react-native"; -import { - Appbar, - Button, - Dialog, - PaperProvider, - Paragraph, - Portal, - Snackbar, -} from "react-native-paper"; +import { Appbar, PaperProvider, Snackbar } from "react-native-paper"; import { SafeAreaView } from "react-native-safe-area-context"; import { getUnique } from "~/Utility"; +import DialogComponent from "~/components/dialog"; import DrillList from "~/components/drillList"; import ErrorComponent from "~/components/errorComponent"; import Loading from "~/components/loading"; @@ -207,7 +200,7 @@ function Index(props) { } }; - const uniqueDrills = getUnique(attempts, "did", Object.values(drillInfo)); + const uniqueDrills = getUnique(attempts, Object.values(drillInfo)); return ( @@ -219,7 +212,7 @@ function Index(props) { {snackbarMessage} - + {/* setDialogVisible(false)} @@ -232,7 +225,16 @@ function Index(props) { - + */} + + setDialogVisible(false)} + buttons={["OK"]} + buttonsFunctions={[() => setDialogVisible(false)]} + /> diff --git a/app/content/team/users/[user]/index.js b/app/content/team/users/[user]/index.js index 45a106a9..10476bb8 100644 --- a/app/content/team/users/[user]/index.js +++ b/app/content/team/users/[user]/index.js @@ -42,7 +42,7 @@ function Index(props) { ); } - const uniqueDrills = getUnique(attempts, "did", Object.values(drillInfo)); + const uniqueDrills = getUnique(attempts, Object.values(drillInfo)); return ( diff --git a/app/segments/drill/[id]/submission/index.js b/app/segments/drill/[id]/submission/index.js index 37b8136d..3a4e07df 100644 --- a/app/segments/drill/[id]/submission/index.js +++ b/app/segments/drill/[id]/submission/index.js @@ -6,10 +6,11 @@ import Loading from "~/components/loading"; import Input from "./input"; import Result from "./result"; +import ErrorComponent from "~/components/errorComponent"; import { useDrillInfo } from "~/hooks/useDrillInfo"; export default function Index() { - const { id, assignedTime } = useLocalSearchParams(); + const { id } = useLocalSearchParams(); const [outputData, setOutputData] = useState([]); const [toggleResult, setToggleResult] = useState(false); @@ -25,7 +26,7 @@ export default function Index() { if (drillInfoError) return ; const display = () => { - if (toggleResult == true) { + if (toggleResult) { return ( { + const updatedAssignedData = assignedData.map((assignment) => { if ( assignment.assignedTime === assignedTime && assignment.drillId === drillId @@ -86,6 +77,7 @@ async function completeAssigned( getDocument(); } +//A function to upload the outputData to the "attempts" collection async function uploadAttempt( outputData, userId, @@ -110,6 +102,9 @@ async function uploadAttempt( await setDoc(newAttemptRef, uploadData) .then(() => { console.log("Document successfully uploaded!"); + //TODO: Call function to check for leaderboard update + + //Check if drill was assigned if (assignedTime) { completeAssigned( userId, @@ -130,6 +125,8 @@ async function uploadAttempt( } } +//TODO: Create a function to check leaderboard and update if needed + /*************************************** * AttemptShots Generation ***************************************/ @@ -147,7 +144,6 @@ function getShotInfo(drillInfo) { break; default: console.log("Shots not found"); - shots = []; break; } @@ -218,7 +214,7 @@ function fillPuttTargets(drillInfo) { //Helper funciton for createOutputData to calculate the Carry Difference function calculateProxHole(target, carry, sideLanding) { let carryDiff = calculateCarryDiff(target, carry); - return Math.sqrt(2 * Math.pow(carryDiff * 3, 2)); + return Math.sqrt(Math.pow(carryDiff * 3, 2) + Math.pow(sideLanding, 2)); } //Helper funciton for createOutputData to calculate the Carry Difference function calculateCarryDiff(target, carry) { @@ -336,7 +332,7 @@ function createOutputData(drillInfo, inputValues, attemptShots, uid, did) { } //add the sid to the shot - shot.sid = j; + shot.sid = j + 1; //push the shot into the array outputShotData.push(shot); @@ -395,6 +391,16 @@ function createOutputData(drillInfo, inputValues, attemptShots, uid, did) { return outputData; } +//A function to validate inputs are not empty +function checkEmptyInputs(inputs) { + return Object.values(inputs).some((value) => value === ""); +} + +//A function to validate inputs are all numbers +function validateInputs(inputs) { + return Object.values(inputs).some((input) => isNaN(input)); +} + export default function Input({ drillInfo, setToggleResult, setOutputData }) { //Helper varibles const { id, assignedTime } = useLocalSearchParams(); @@ -431,50 +437,36 @@ export default function Input({ drillInfo, setToggleResult, setOutputData }) { const descriptionModalRef = useRef(null); /***** Leave drill Dialog Stuff *****/ - const [leaveDialogVisible, setLeaveDialogVisible] = useState(false); const hideLeaveDialog = () => setLeaveDialogVisible(false); - /***** Empty Input Banner Stuff *****/ + /***** Empty Input dialog Stuff *****/ + const [emptyDialogVisible, setEmptyDialogVisible] = useState(false); + const hideEmptyDialog = () => setEmptyDialogVisible(false); - const [emptyInputBannerVisible, setEmptyInputBannerVisible] = useState(false); + /***** Invalid Input dialog Stuff *****/ + const [invalidDialogVisible, setInvalidDialogVisible] = useState(false); + const hideInvalidDialog = () => setInvalidDialogVisible(false); //useEffectHook to set the attempts shot requirements useEffect(() => { setattemptShots(getShotInfo(drillInfo)); }, []); + //Varible to store if Submit button is active + const submitVisible = + currentShot === drillInfo.reps - 1 && displayedShot === drillInfo.reps - 1; + //Changes the button depending on the current shot and shot index const buttonDisplayHandler = () => { //Logic to display "Submit Drill" - if ( - currentShot == drillInfo.reps - 1 && - displayedShot == drillInfo.reps - 1 - ) { + if (submitVisible) { return ( @@ -503,7 +495,7 @@ export default function Input({ drillInfo, setToggleResult, setOutputData }) { style={styles.button} labelStyle={styles.buttonText} mode="contained-tonal" - onPress={handleNextShotButtonClick} + onPress={handleButtonClick} > Next Shot @@ -523,14 +515,42 @@ export default function Input({ drillInfo, setToggleResult, setOutputData }) { }; //Function to handle "Next shot" button click - const handleNextShotButtonClick = () => { + const handleButtonClick = () => { //Check if all inputs have been filled in - if (Object.keys(inputValues[displayedShot]).length === numInputs) { - setEmptyInputBannerVisible(false); + if ( + Object.keys(inputValues[displayedShot]).length !== numInputs || + checkEmptyInputs(inputValues[displayedShot]) + ) { + setEmptyDialogVisible(true); + } + //check inputs are all numbers + else if (validateInputs(inputValues[displayedShot])) { + setInvalidDialogVisible(true); + } + //check for submit button + else if (submitVisible) { + let outputData = createOutputData( + inputValues, + attemptShots, + currentUserId, + did, + drillInfo.outputs, + drillInfo.aggOutputs, + ); + + setOutputData(outputData); + uploadAttempt( + outputData, + currentUserId, + currentTeamId, + assignedTime, + id, + queryClient, + ); + setToggleResult(true); + } else { setDisplayedShot(displayedShot + 1); setCurrentShot(currentShot + 1); - } else { - setEmptyInputBannerVisible(true); } }; @@ -564,19 +584,6 @@ export default function Input({ drillInfo, setToggleResult, setOutputData }) { color={"#F24E1E"} /> - {/* Empty Input Banner */} - - setEmptyInputBannerVisible(false), - }, - ]} - > - Error! All input fields must be filled! - {/* Shot Number / Total shots */} @@ -608,6 +615,7 @@ export default function Input({ drillInfo, setToggleResult, setOutputData }) { key={id} icon={getIconByKey(item.id)} prompt={item.prompt} + helperText={item.helperText} distanceMeasure={item.distanceMeasure} inputValue={inputValues[displayedShot]?.[item.id] || ""} onInputChange={(newText) => { @@ -664,28 +672,40 @@ export default function Input({ drillInfo, setToggleResult, setOutputData }) { {/* Leave Drill Dialog */} - - - Alert - - All inputs will be lost. - - - - - - - + { + hideLeaveDialog; + navigation.goBack(); + }, + ]} + /> + + {/* Error Dialog: Empty Input*/} + + + {/* Error Dialog: Invalid Input*/} + {/* Navigation */} diff --git a/components/dialog.js b/components/dialog.js new file mode 100644 index 00000000..fea8d445 --- /dev/null +++ b/components/dialog.js @@ -0,0 +1,58 @@ +import { Button, Dialog, Portal, Text } from "react-native-paper"; + +/** + * PROPS + * title - title to be displayed on the dialog + * content - main text to be displayed on the dialog + * visible - from useState Hook for this Dialog + * onHide - function to set useState Hook to false + * buttons - an array of strings for the buttons to be displayed + * buttonsFunctions - an array of functions for the buttons to be displayed + */ +export default function DialogComponent({ + title, + content, + visible, + onHide, + buttons, + buttonsFunctions, +}) { + const Buttons = buttons.map((item, index) => { + let style; + let labelStyle; + if (index === 0) { + style = {}; + labelStyle = { color: "#F24E1E" }; + } else { + style = { backgroundColor: "#F24E1E" }; + labelStyle = { color: "white" }; + } + + return ( + + ); + }); + + return ( + + + {title} + + {content} + + {Buttons} + + + ); +} diff --git a/components/input/drillInput.js b/components/input/drillInput.js index dfddc534..4bc1433f 100644 --- a/components/input/drillInput.js +++ b/components/input/drillInput.js @@ -4,6 +4,7 @@ import { Icon, Text, TextInput } from "react-native-paper"; export default function DrillInput({ icon, prompt, + helperText, distanceMeasure, inputValue, onInputChange, @@ -29,6 +30,7 @@ export default function DrillInput({ /> {distanceMeasure} + {helperText} ); } @@ -43,12 +45,16 @@ const styles = StyleSheet.create({ }, description: { fontSize: 20, - fontWeight: "bold", //temporary until I get the fonts to work + fontWeight: "bold", marginBottom: 10, }, distance: { fontSize: 40, - fontWeight: "200", //temporary until I get the fonts to work + fontWeight: "200", marginLeft: 10, }, + helper: { + fontSize: 12, + fontWeight: "200", + }, });