diff --git a/app/(interactive)/matches/[slug]/page.js b/app/(interactive)/matches/[slug]/page.js index 0bfe295..207e820 100644 --- a/app/(interactive)/matches/[slug]/page.js +++ b/app/(interactive)/matches/[slug]/page.js @@ -10,6 +10,7 @@ import VideoPlayer from '../../../components/VideoPlayer'; import FilterList from '../../../components/FilterList'; import PointsList from '../../../components/PointsList'; import ScoreBoard from '../../../components/ScoreBoard'; +import MatchTiles from '@/app/components/MatchTiles'; // delete later just for testing import { collection, getDocs } from 'firebase/firestore'; import { db } from '../../../services/initializeFirebase'; @@ -119,6 +120,7 @@ const MatchPage = () => { {/* Main Content Area */} {matchData && ( <> +

{matchData.name}

diff --git a/app/(interactive)/upload-video/page.js b/app/(interactive)/upload-video/page.js index 0c11907..0c83f4b 100644 --- a/app/(interactive)/upload-video/page.js +++ b/app/(interactive)/upload-video/page.js @@ -1,7 +1,8 @@ 'use client' -import React, { useState } from 'react'; +import React, { useState, useEffect, useMemo } from 'react'; import uploadMatch from '../../services/uploadMatch.js'; +import getLogos from '@/app/services/getLogos.js'; import styles from '../../styles/Upload.module.css' @@ -10,39 +11,47 @@ export default function UploadVideo() { const [videoId, setVideoId] = useState(''); const [jsonFile, setJsonFile] = useState(null); const [pdfFile, setPdfFile] = useState(null); + const [clientLogo, setClientLogo] = useState('arizona_state'); + const [opponentLogo, setOpponentLogo] = useState('arizona_state'); + const [logos, setLogos] = useState([]); - const handleMatchNameChange = (e) => { - setMatchName(e.target.value); - }; - - const handleVideoIdChange = (e) => { - setVideoId(e.target.value); - }; + useEffect(() => { + const fetchLogos = async () => { + try { + const logos = await getLogos(); + setLogos(logos); + } catch (error) { + console.error('Error fetching data:', error); + } + }; - const handleJsonFileChange = (e) => { - setJsonFile(e.target.files[0]); - }; - - const handlePdfFileChange = (e) => { - setPdfFile(e.target.files[0]); - }; + fetchLogos(); + }, []); const handleSubmit = async (e) => { e.preventDefault(); - if (!matchName || !videoId || !jsonFile) { + if (!matchName || !videoId || !jsonFile || !clientLogo || !opponentLogo) { console.error("Please fill in all fields."); return; } try { const pointsJson = JSON.parse(await jsonFile.text()); - await uploadMatch(matchName, videoId, pointsJson, pdfFile); + const clientLogoURL = logos.find((logo) => logo.name === clientLogo).downloadURL; + const opponentLogoURL = logos.find((logo) => logo.name === opponentLogo).downloadURL; + await uploadMatch(matchName, videoId, pointsJson, pdfFile, clientLogoURL, opponentLogoURL); + alert('done!') } catch (error) { console.error("Error uploading match:", error); } }; - + + const logosOptions = useMemo(() => { + return logos.map((option, index) => ( + + )); + }, [logos]); return (
@@ -50,19 +59,31 @@ export default function UploadVideo() {
+ +
diff --git a/app/components/MatchTiles.js b/app/components/MatchTiles.js new file mode 100644 index 0000000..36ab223 --- /dev/null +++ b/app/components/MatchTiles.js @@ -0,0 +1,290 @@ +import React, { useState, useEffect } from "react"; +import styles from "../styles/MatchTiles.module.css"; + +const extractSetScore = (setObject) => { + // no third set + if (!setObject) return { score: "", type: "" }; + const setType = (setObject.tiebreakScore !== null && setObject.tiebreakScore !== "" && setObject.tiebreakScore !== undefined) ? "tiebreakScore" : "gameScore"; + return { + // score: setType === "gameScore" ? setObject.gameScore : setObject.tiebreakScore, + score: setObject.gameScore, + type: setType + }; +}; + +// score: '5-6', score[0] is 5 and score[2] is 6 +const extractPlayerFinalScores = (setScores, playerName, i) => { + return setScores.map(setScore => ({ + score: parseInt(setScore.score[i]), + playerName: playerName + })); +}; + +//Calculate winner of match +const calculateWinner = (playerOne, playerTwo) => { + const playerOneTotal = playerOne.reduce((total, current) => { + if (!isNaN(current.score)) { + return total + current.score; + } else { + return total; + } + }, 0); + const playerTwoTotal = playerTwo.reduce((total, current) => { + if (!isNaN(current.score)) { + return total + current.score; + } else { + return total; + } + }, 0); + return playerOneTotal > playerTwoTotal; +}; + +// Retrieve team information +const isWomensTeam = (match) => { + return match.includes("(W)"); +}; + +//Retrieve Match Date +const extractDateFromString = (inputString) => { + const regex = /\b(\d{1,2}\/\d{1,2}\/\d{2,4})\b/g; + const matches = inputString.match(regex); + if (matches) { + // Assuming there might be multiple date patterns in the string, return an array of matches + const firstMatch = matches[0]; // Assuming you want to pick the first matched date + const dateParts = firstMatch.split("/"); + const month = parseInt(dateParts[0]); + const day = parseInt(dateParts[1]); + const year = parseInt(dateParts[2]); + + // Create a new Date object with the extracted components + const dateObject = new Date(year + 2000, month - 1, day); + + const formattedDate = dateObject.toLocaleDateString("en-US", { + month: "long", + day: "numeric", + year: "numeric", + }); + + return formattedDate; + } else { + return null; + } +}; + +const MatchTiles = ({ + matchName, + finalScore, + clientLogo, + opposingLogo, + matchDetails, +}) => { + //Tile heights + const [isUnfinished, setIsUnfinished] = useState(false); + + //Extract Final Scores from Each Set + const firstSetObject = finalScore.filter((score) => score.setNum === 1).pop(); + const secondSetObject = finalScore.filter((score) => score.setNum === 2).pop(); + const thirdSetObject = finalScore.filter((score) => score.setNum === 3).pop(); + const setObjects = [firstSetObject, secondSetObject, thirdSetObject]; + // // Extract Scores and type of Each Set + const setScores = [extractSetScore(firstSetObject), extractSetScore(secondSetObject), extractSetScore(thirdSetObject)]; + + // Extract Player Names and Assign Scores for Each Player + // player1 is client, player2 is opponent + const playerOneName = firstSetObject.player1Name; + const playerTwoName = firstSetObject.player2Name; + const playerOneFinalScores = extractPlayerFinalScores(setScores, playerOneName, 0); + const playerTwoFinalScores = extractPlayerFinalScores(setScores, playerTwoName, 2); + + //Tiebreaker scores array + const playerOneTieScores = Array(3); + const playerTwoTieScores = Array(3); + + // Check if sets are tiebreaks + // Non tiebreak score winners are given winning point + const processSet = ( + setScores, + setObjects, + playerOneFinalScores, + playerTwoFinalScores, + playerOneTieScores, + playerTwoTieScores, + index + ) => { + if (setScores[index].type == "gameScore") { + //Check if unfinished match + if (playerOneFinalScores[index].score < 5 && playerTwoFinalScores[index].score < 5 && !isUnfinished) { + setIsUnfinished(true); + } + //Increment winners score by 1 + if (playerOneFinalScores[index].score > playerTwoFinalScores[index].score) { + playerOneFinalScores[index].score++; + } else { + playerTwoFinalScores[index].score++; + } + } else if (setScores[index].type == "tiebreakScore") { + //Compare tiebreak scores and increment winner + playerOneTieScores[index] = parseInt(setObjects[index].tiebreakScore[0]); + playerTwoTieScores[index] = parseInt(setObjects[index].tiebreakScore[2]); + //check if unfinished match + if (playerOneFinalScores[index].score < 5 && playerTwoFinalScores[index].score < 5 && !isUnfinished) { + setIsUnfinished(true); + } + if (playerOneTieScores[index] > playerTwoTieScores[index]) { + playerOneFinalScores[index].score++; + // Tiebreak more than 7 points + if (playerTwoTieScores[index] >= 6) { + playerOneTieScores[index]++; + } + } else { + if (playerOneTieScores[index] >= 6) { + playerTwoTieScores[index]++; + } + playerTwoFinalScores[index].score++; + } + } else { + return; + } + }; + + // Process each set score. + setScores.forEach((_, index) => { + processSet( + setScores, + setObjects, + playerOneFinalScores, + playerTwoFinalScores, + playerOneTieScores, + playerTwoTieScores, + index + ); + }); + + return ( +
+
+
Final Score
+ {/* Player Information */} +
+
+ +
+
+ {playerOneName} {isUnfinished && "(UF)"} +
+
+ {/* Check if tie break, if so add exponent */} + {playerOneFinalScores.map((score, index) => + isNaN(score.score) ? null : ( +
+ {playerOneTieScores[index] ? ( + + {score.score} + + {playerOneTieScores[index]} + + + ) : ( + {score.score} + )} +
+ ) + )} +
+
+
+
+ +
+
+ {playerTwoName} +
+
+ {playerTwoFinalScores.map((score, index) => + isNaN(score.score) ? null : ( +
+ {playerTwoTieScores[index] ? ( +
+ {score.score} + + {playerTwoTieScores[index]} + +
+ ) : ( + {score.score} + )} +
+ ) + )} +
+
+
+ {/* Match Location */} +
+
Match Information
+
{matchDetails}
+
+ {extractDateFromString(matchName)} +
+
+ {/* School Info */} +
+
Matchup
+
+ UCLA {isWomensTeam(matchName) && "(Womens)"} +
+
+ {finalScore[0].opponentTeam} {isWomensTeam(matchName) && "(Womens)"} +
+
+
+ ); +}; + +export default MatchTiles; diff --git a/app/services/getLogos.js b/app/services/getLogos.js new file mode 100644 index 0000000..3a85613 --- /dev/null +++ b/app/services/getLogos.js @@ -0,0 +1,21 @@ +import { db, storage } from '../services/initializeFirebase.js'; // Ensure storage is exported from initializeFirebase.js +import { ref, listAll, getDownloadURL } from "firebase/storage"; // Import storage functions + +const logosRef = ref(storage, 'logos'); + +const getLogos = async() => { + try { + const files = await listAll(logosRef); + const logos = await Promise.all(files.items.map(async (item) => { + const downloadURL = await getDownloadURL(item); + const parsedName = item.name.split('.')[0]; + return { name: parsedName, downloadURL }; + })); + return logos; + } catch (error) { + console.error('Error retrieving files:', error); + throw error; + } +}; + +export default getLogos; \ No newline at end of file diff --git a/app/services/uploadMatch.js b/app/services/uploadMatch.js index 52e5831..89278db 100644 --- a/app/services/uploadMatch.js +++ b/app/services/uploadMatch.js @@ -3,8 +3,9 @@ import { ref, uploadBytes, getDownloadURL } from "firebase/storage"; // Import s import { db, storage } from '../services/initializeFirebase.js'; // Ensure storage is exported from initializeFirebase.js // Define the uploadMatch function -async function uploadMatch(matchName, videoId, pointsJson, pdfFile) { - if (!matchName || !videoId || !pointsJson) { +async function uploadMatch(matchName, videoId, pointsJson, pdfFile, clientLogo, opponentLogo) { + console.log(clientLogo) + if (!matchName || !videoId || !pointsJson || !clientLogo || !opponentLogo) { console.error("All fields are required."); return; // Exit the function if any field is empty } @@ -23,7 +24,9 @@ async function uploadMatch(matchName, videoId, pointsJson, pdfFile) { name: matchName, videoId: videoId, points: pointsJson, - pdfUrl: pdfUrl + pdfUrl: pdfUrl, + clientLogo: clientLogo, + opponentLogo: opponentLogo }); console.log("Document written with ID: ", docRef.id); diff --git a/app/styles/MatchTiles.module.css b/app/styles/MatchTiles.module.css new file mode 100644 index 0000000..afb9fd0 --- /dev/null +++ b/app/styles/MatchTiles.module.css @@ -0,0 +1,57 @@ +@import url('https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&display=swap'); + +.matchTilesContainer { + display: flex; + width: 100%; +} + +.matchInfoContainer { + border: 1px solid #DEDEDE; + border-radius: 24px; + font-family: 'DM Sans', sans-serif; + width: 38%; + margin: 8px; + padding: 1em; +} + +.containerTitle { + font-size: 1.1vw; + font-weight: 350; + padding-bottom: 2px; +} + +.containerInfo { + font-size: 1.8vw; + font-weight: 600; +} + +.playerInfo { + display: flex; + font-size: 1.8vw; + font-weight: 600; + position: relative; +} + +.playerInfoName { + padding-left: 1em; + padding-right: 5em; + display: flex; + font-size: 1.8vw; + font-weight: 600; + overflow: hidden; + white-space: nowrap; +} + +.playerInfoScore { + position: absolute; + display: flex; + font-size: 1.8vw; + font-weight: 600; + left: 85%; + letter-spacing: .5em; +} + +.playerSchoolImg { + width: 1.5em; + padding: 0; +} \ No newline at end of file diff --git a/app/styles/Upload.module.css b/app/styles/Upload.module.css index 698bf0c..73d2122 100644 --- a/app/styles/Upload.module.css +++ b/app/styles/Upload.module.css @@ -19,6 +19,7 @@ flex-direction: column; } +.form label select, .form label input{ margin-left: 0.5rem; /* Adjust the margin-left as needed */ } \ No newline at end of file