mirror of
https://github.com/Team4388/ScoutingApp2022.git
synced 2026-06-08 16:28:04 -06:00
this code is better than methamphetamines
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -14,11 +14,13 @@
|
|||||||
"ag-grid-react": "^26.2.0",
|
"ag-grid-react": "^26.2.0",
|
||||||
"formik": "^2.2.9",
|
"formik": "^2.2.9",
|
||||||
"pouchdb": "^7.2.2",
|
"pouchdb": "^7.2.2",
|
||||||
|
"delta-pouch": "^1.0.2",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-router": "^6.2.1",
|
"react-router": "^6.2.1",
|
||||||
"react-router-dom": "^6.2.1",
|
"react-router-dom": "^6.2.1",
|
||||||
"react-scripts": "5.0.0",
|
"react-scripts": "5.0.0",
|
||||||
|
"superagent": "^1.7.2",
|
||||||
"web-vitals": "^2.1.3",
|
"web-vitals": "^2.1.3",
|
||||||
"@mui/material": "^5.5.0",
|
"@mui/material": "^5.5.0",
|
||||||
"@mui/icons-material": "^5.5.0",
|
"@mui/icons-material": "^5.5.0",
|
||||||
|
|||||||
+12
-4
@@ -9,7 +9,11 @@ import "./App.css";
|
|||||||
import NotFoundPage from "./Pages/NotFoundPage";
|
import NotFoundPage from "./Pages/NotFoundPage";
|
||||||
import DashboardPage from "./Pages/DashboardPage/DashboardPage";
|
import DashboardPage from "./Pages/DashboardPage/DashboardPage";
|
||||||
import WelcomePage from "./Pages/WelcomePage";
|
import WelcomePage from "./Pages/WelcomePage";
|
||||||
|
import SchedulePage from "./Pages/SchedulePage";
|
||||||
import InputPage from "./Pages/InputPage";
|
import InputPage from "./Pages/InputPage";
|
||||||
|
import TeamListPage from "./Pages/TeamListPage";
|
||||||
|
import DevPage from "./Pages/DevPage";
|
||||||
|
import TeamPage from "./Pages/TeamPage";
|
||||||
import { createTheme, ThemeProvider } from "@mui/material";
|
import { createTheme, ThemeProvider } from "@mui/material";
|
||||||
import { ProcessedDataBucketProvider } from "./ProcessedDataBucketContext";
|
import { ProcessedDataBucketProvider } from "./ProcessedDataBucketContext";
|
||||||
import NotesPage from "./Pages/NotesPage";
|
import NotesPage from "./Pages/NotesPage";
|
||||||
@@ -35,8 +39,8 @@ function App() {
|
|||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<ThemeProvider theme={darkTheme}>
|
<ThemeProvider theme={darkTheme}>
|
||||||
<ProcessedDataBucketProvider>
|
<DbProvider>
|
||||||
<DbProvider>
|
<ProcessedDataBucketProvider>
|
||||||
<Router>
|
<Router>
|
||||||
<Navigation />
|
<Navigation />
|
||||||
<div className="App">
|
<div className="App">
|
||||||
@@ -45,13 +49,17 @@ function App() {
|
|||||||
<Route path="/Dashboard" element={<DashboardPage />} />
|
<Route path="/Dashboard" element={<DashboardPage />} />
|
||||||
<Route path="/Input" element={<InputPage />} />
|
<Route path="/Input" element={<InputPage />} />
|
||||||
<Route path="/Notes" element={<NotesPage />} />
|
<Route path="/Notes" element={<NotesPage />} />
|
||||||
|
<Route path="/Schedule" element={<SchedulePage />} />
|
||||||
|
<Route path="/TeamList" element={<TeamListPage />} />
|
||||||
|
<Route path="/Dev" element={<DevPage />} />
|
||||||
|
<Route path="/Team" element={<TeamPage />} />
|
||||||
<Route path="/404" element={<NotFoundPage />} />
|
<Route path="/404" element={<NotFoundPage />} />
|
||||||
<Route path="*" element={<NotFoundPage />} />
|
<Route path="*" element={<NotFoundPage />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</div>
|
</div>
|
||||||
</Router>
|
</Router>
|
||||||
</DbProvider>
|
</ProcessedDataBucketProvider>
|
||||||
</ProcessedDataBucketProvider>
|
</DbProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+75
-28
@@ -3,6 +3,8 @@ import React, { useContext, useEffect, useState, useCallback } from "react";
|
|||||||
import { useProcessedDataBucket } from "./ProcessedDataBucketContext";
|
import { useProcessedDataBucket } from "./ProcessedDataBucketContext";
|
||||||
import { getProcessedDataBucket, updateProcessedDataBucket } from "./ProcessedDataBucket";
|
import { getProcessedDataBucket, updateProcessedDataBucket } from "./ProcessedDataBucket";
|
||||||
|
|
||||||
|
PouchDB.plugin(require("delta-pouch"));
|
||||||
|
|
||||||
const LocalDbContext = React.createContext();
|
const LocalDbContext = React.createContext();
|
||||||
const RemoteDbContext = React.createContext();
|
const RemoteDbContext = React.createContext();
|
||||||
|
|
||||||
@@ -16,32 +18,79 @@ export function useRemoteDb() {
|
|||||||
|
|
||||||
export function DbProvider({ children }) {
|
export function DbProvider({ children }) {
|
||||||
// const pdbCtx = useProcessedDataBucket();
|
// const pdbCtx = useProcessedDataBucket();
|
||||||
const { processedDataBucket, setProcessedDataBucket } = useProcessedDataBucket();
|
// const { setProcessedDataBucket } = useProcessedDataBucket();
|
||||||
// console.log(pdb);
|
// console.log(pdb);
|
||||||
const [localdb, setLocaldb] = useState(new PouchDB("denver_fr"));
|
// const [localdb, setLocaldb] = useState(null);
|
||||||
|
// const [remotedb, setRemotedb] = useState(null);
|
||||||
|
// useEffect(() => {
|
||||||
|
// // setDatabaseName("denver_fr", setLocaldb, setRemotedb, setProcessedDataBucket);
|
||||||
|
// });
|
||||||
|
|
||||||
|
const [localdb, setLocaldb] = useState(new PouchDB("utah_fr"));
|
||||||
const [remotedb, setRemotedb] = useState(
|
const [remotedb, setRemotedb] = useState(
|
||||||
new PouchDB("http://" + window.location.hostname + ":5984/denver_fr", {
|
new PouchDB("http://" + window.location.hostname + ":5984/utah_fr", {
|
||||||
skip_setup: true,
|
// skip_setup: true,
|
||||||
auth: {
|
auth: {
|
||||||
username: "scouting",
|
username: "scouting",
|
||||||
password: "Ridgebotics",
|
password: "Ridgebotics",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
localdb.replicate
|
localdb
|
||||||
.from(remotedb, {
|
// .setMaxListeners(400)
|
||||||
|
.sync(remotedb, {
|
||||||
|
// localdb
|
||||||
|
// .sync(remotedb, {
|
||||||
live: true,
|
live: true,
|
||||||
retry: true,
|
retry: true,
|
||||||
})
|
|
||||||
.on("change", (change) => {
|
|
||||||
updateProcessedDataBucket(localdb, setProcessedDataBucket);
|
|
||||||
});
|
});
|
||||||
// useEffect(()=>{
|
// const [syncHandle, setSyncHandle] = useState(
|
||||||
|
// null
|
||||||
|
// // localdb
|
||||||
|
// // // .setMaxListeners(400)
|
||||||
|
// // .sync(remotedb, {
|
||||||
|
// // // localdb
|
||||||
|
// // // .sync(remotedb, {
|
||||||
|
// // live: true,
|
||||||
|
// // retry: true,
|
||||||
|
// // })
|
||||||
|
// // .on("complete", (info) => {
|
||||||
|
// // console.log("REPLICATION CANCELLED");
|
||||||
|
// // console.log(info);
|
||||||
|
// // })
|
||||||
|
// );
|
||||||
|
// if (syncHandle != null && typeof syncHandle.cancel !== "undefined") syncHandle.cancel();
|
||||||
|
|
||||||
|
// localdb.on("change", (change) => {
|
||||||
|
// console.log("CHANGE");
|
||||||
|
// updateProcessedDataBucket(localdb, setProcessedDataBucket);
|
||||||
|
// });
|
||||||
|
|
||||||
|
// .catch(console.log);
|
||||||
|
localdb.on("update", console.log);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// handle.cancel();
|
||||||
|
console.log("SYNCSYNCSYNCSYNCSYNC");
|
||||||
|
// updateProcessedDataBucket(localdb, setProcessedDataBucket);
|
||||||
|
// localdb
|
||||||
|
// // .setMaxListeners(400)
|
||||||
|
// .sync(remotedb, {
|
||||||
|
// // localdb
|
||||||
|
// // .sync(remotedb, {
|
||||||
|
// live: true,
|
||||||
|
// retry: true,
|
||||||
|
// });
|
||||||
|
// .on("complete", (info) => {
|
||||||
|
// console.log("REPLICATION CANCELLED");
|
||||||
|
// console.log(info);
|
||||||
|
// })
|
||||||
|
}, [localdb, remotedb]);
|
||||||
|
|
||||||
|
// localdb.replicate.to(remotedb, {
|
||||||
|
// retry: true,
|
||||||
|
// });
|
||||||
// updateProcessedDataBucket(localdb, setProcessedDataBucket);
|
// updateProcessedDataBucket(localdb, setProcessedDataBucket);
|
||||||
// }, [setProcessedDataBucket]);
|
|
||||||
// localdb.replicate.to(remotedb, {
|
|
||||||
// retry: true,
|
|
||||||
// });
|
|
||||||
|
|
||||||
// const [localNotesdb, setLocalNotesdb] = useState(new PouchDB("denver_notes"));
|
// const [localNotesdb, setLocalNotesdb] = useState(new PouchDB("denver_notes"));
|
||||||
// const [remoteNotesdb, setRemoteNotesdb] = useState(
|
// const [remoteNotesdb, setRemoteNotesdb] = useState(
|
||||||
@@ -62,7 +111,6 @@ export function DbProvider({ children }) {
|
|||||||
// // updateProcessedDataBucket(localdb, setProcessedDataBucket);
|
// // updateProcessedDataBucket(localdb, setProcessedDataBucket);
|
||||||
// });
|
// });
|
||||||
|
|
||||||
|
|
||||||
// updateProcessedDataBucket(localdb, setProcessedDataBucket);
|
// updateProcessedDataBucket(localdb, setProcessedDataBucket);
|
||||||
// useEffect(() => {
|
// useEffect(() => {
|
||||||
// setDatabaseName("denver_fr", setLocaldb, setRemotedb, setProcessedDataBucket);
|
// setDatabaseName("denver_fr", setLocaldb, setRemotedb, setProcessedDataBucket);
|
||||||
@@ -116,19 +164,18 @@ export function setDatabaseName(name, setLocaldb, setRemotedb, setProcessedDataB
|
|||||||
password: "Ridgebotics",
|
password: "Ridgebotics",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
localdb.replicate
|
localdb.sync(remotedb, {
|
||||||
.from(remotedb, {
|
live: true,
|
||||||
live: true,
|
retry: true,
|
||||||
retry: true,
|
});
|
||||||
})
|
// .on("change", (change) => {
|
||||||
.on("change", (change) => {
|
// updateProcessedDataBucket(localdb, setProcessedDataBucket);
|
||||||
updateProcessedDataBucket(localdb, setProcessedDataBucket);
|
// });
|
||||||
});
|
// updateProcessedDataBucket(localdb, setProcessedDataBucket);
|
||||||
updateProcessedDataBucket(localdb, setProcessedDataBucket);
|
// localdb.replicate.to(remotedb, {
|
||||||
localdb.replicate.to(remotedb, {
|
// live: true,
|
||||||
live: true,
|
// retry: true,
|
||||||
retry: true,
|
// });
|
||||||
});
|
|
||||||
setLocaldb(localdb);
|
setLocaldb(localdb);
|
||||||
setRemotedb(remotedb);
|
setRemotedb(remotedb);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,107 @@
|
|||||||
|
import React, { useCallback } from "react";
|
||||||
|
import { Formik, FastField, Form, useFormikContext } from "formik";
|
||||||
|
import { TextField, Button, Grid, FormRow, Divider, Checkbox, Radio, FormControlLabel, FormControl, FormLabel, RadioGroup, IconButton, NotesAdornment, Box } from "@mui/material";
|
||||||
|
import { useLocalDb, useRemoteDb } from "../DbContext";
|
||||||
|
import { useProcessedDataBucket } from "../ProcessedDataBucketContext";
|
||||||
|
import { getProcessedDataBucket, updateProcessedDataBucket } from "../ProcessedDataBucket";
|
||||||
|
import Schedule from "../components/Schedule";
|
||||||
|
import superagent from "superagent";
|
||||||
|
|
||||||
|
const DevPage = () => {
|
||||||
|
let { localdb, setLocaldb } = useLocalDb();
|
||||||
|
const putScheduleDoc = useCallback(
|
||||||
|
(doc) => {
|
||||||
|
localdb.save({
|
||||||
|
$id: "schedule",
|
||||||
|
type: "schedule",
|
||||||
|
matches: doc,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[localdb]
|
||||||
|
);
|
||||||
|
const putTeamListDoc = useCallback(
|
||||||
|
(doc) => {
|
||||||
|
localdb.save({
|
||||||
|
$id: "team_list",
|
||||||
|
type: "team_list",
|
||||||
|
teams: doc,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[localdb]
|
||||||
|
);
|
||||||
|
const processScheduleData = (data) => {
|
||||||
|
data.sort((a, b) => a.predicted_time - b.predicted_time);
|
||||||
|
let ret = [];
|
||||||
|
console.log(data);
|
||||||
|
for (let match of data) {
|
||||||
|
// console.log(match);
|
||||||
|
let red = [];
|
||||||
|
let blue = [];
|
||||||
|
|
||||||
|
for (let team_key of match.alliances.red.team_keys) {
|
||||||
|
red.push(team_key.substring(3));
|
||||||
|
}
|
||||||
|
for (let team_key of match.alliances.blue.team_keys) {
|
||||||
|
blue.push(team_key.substring(3));
|
||||||
|
}
|
||||||
|
|
||||||
|
ret.push({
|
||||||
|
match_id: match.key.split("_")[1],
|
||||||
|
// ret[match.key.split("_")[1]] = {
|
||||||
|
// ret["yo"] = {
|
||||||
|
red: red,
|
||||||
|
blue: blue,
|
||||||
|
score_breakdown: match.score_breakdown,
|
||||||
|
});
|
||||||
|
// console.log(match);
|
||||||
|
}
|
||||||
|
// console.log(ret);
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
|
||||||
|
const processTeamData = (data) => {
|
||||||
|
data.sort((a, b) => a.key > b.key);
|
||||||
|
let ret = {};
|
||||||
|
for (let team of data) {
|
||||||
|
ret[team.team_number] = {
|
||||||
|
nickname: team.nickname,
|
||||||
|
number: team.team_number,
|
||||||
|
website: team.website,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateScheduleDoc = useCallback(() => {
|
||||||
|
//gets the matches list from the blue alliance api
|
||||||
|
superagent
|
||||||
|
.get("https://www.thebluealliance.com/api/v3" + "/event/2022code/matches")
|
||||||
|
// .get("https://www.thebluealliance.com/api/v3" + "/event/2022utwv/matches")
|
||||||
|
// .get("https://www.thebluealliance.com/api/v3" + "/event/2022cala/matches")
|
||||||
|
// .get("https://www.thebluealliance.com/api/v3" + "/events/2022/keys")
|
||||||
|
.set("X-TBA-Auth-Key", "6aXgVYCAcyy4O7FwCGLqj5ATcima5k25smssLqUuHAHTCvGtCWXX7aoM9xNWfaSm")
|
||||||
|
.end((err, res) => {
|
||||||
|
//parse the resulting json and send it to the db
|
||||||
|
putScheduleDoc(processScheduleData(JSON.parse(res.text)));
|
||||||
|
});
|
||||||
|
superagent
|
||||||
|
.get("https://www.thebluealliance.com/api/v3" + "/event/2022utwv/teams")
|
||||||
|
.set("X-TBA-Auth-Key", "6aXgVYCAcyy4O7FwCGLqj5ATcima5k25smssLqUuHAHTCvGtCWXX7aoM9xNWfaSm")
|
||||||
|
.end((err, res) => {
|
||||||
|
//parse the resulting json and send it to the db
|
||||||
|
putTeamListDoc(processTeamData(JSON.parse(res.text)));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const cleanDB = useCallback(() => {
|
||||||
|
localdb.cleanup();
|
||||||
|
}, [localdb]);
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Button onClick={updateScheduleDoc}>Update Matches Doc</Button>
|
||||||
|
<Button onClick={cleanDB}>Cleanup DB</Button>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DevPage;
|
||||||
@@ -33,6 +33,7 @@ const InputPage = () => {
|
|||||||
.put({
|
.put({
|
||||||
// _id: new Date().toISOString(),
|
// _id: new Date().toISOString(),
|
||||||
_id: "match_" + values.match_number + "_team_" + values.team_number,
|
_id: "match_" + values.match_number + "_team_" + values.team_number,
|
||||||
|
_rev: new Date().toISOString(),
|
||||||
type: "match",
|
type: "match",
|
||||||
...values,
|
...values,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import React, { useCallback } from "react";
|
||||||
|
import { Formik, FastField, Form, useFormikContext } from "formik";
|
||||||
|
import { TextField, Button, Grid, FormRow, Divider, Checkbox, Radio, FormControlLabel, FormControl, FormLabel, RadioGroup, IconButton, NotesAdornment, Box } from "@mui/material";
|
||||||
|
import { useProcessedDataBucket } from "../ProcessedDataBucketContext";
|
||||||
|
import { getProcessedDataBucket, updateProcessedDataBucket } from "../ProcessedDataBucket";
|
||||||
|
import Schedule from "../components/Schedule";
|
||||||
|
|
||||||
|
const SchedulePage = () => {
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Schedule />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SchedulePage;
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import React, { useCallback, useState, useEffect } from "react";
|
||||||
|
import { Formik, FastField, Form, useFormikContext } from "formik";
|
||||||
|
import { TextField, Button, Grid, FormRow, Divider, Checkbox, Radio, FormControlLabel, FormControl, FormLabel, RadioGroup, IconButton, NotesAdornment, Box } from "@mui/material";
|
||||||
|
import { useProcessedDataBucket } from "../ProcessedDataBucketContext";
|
||||||
|
import { getProcessedDataBucket, updateProcessedDataBucket } from "../ProcessedDataBucket";
|
||||||
|
import { useLocalDb } from "../DbContext";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
|
const TeamListPage = () => {
|
||||||
|
let { localdb, setLocaldb } = useLocalDb();
|
||||||
|
// let
|
||||||
|
|
||||||
|
let panel_sx = {
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
bgcolor: "background.paper",
|
||||||
|
p: 2,
|
||||||
|
m: 1,
|
||||||
|
gap: 2,
|
||||||
|
maxWidth: "fit-content",
|
||||||
|
borderRadius: "10px",
|
||||||
|
boxShadow: 7,
|
||||||
|
};
|
||||||
|
const [teamList, setTeamList] = useState(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
localdb.all().then((res) => {
|
||||||
|
// console.log(res["schedule"]);
|
||||||
|
setTeamList(res["team_list"]);
|
||||||
|
});
|
||||||
|
}, [setTeamList]);
|
||||||
|
if (teamList == null) return <div />;
|
||||||
|
console.log("test");
|
||||||
|
console.log(teamList);
|
||||||
|
|
||||||
|
const TeamNumberComponent = (props) => {
|
||||||
|
return (
|
||||||
|
<Link to="/Team" state={{ team: props.number }} style={{ color: "inherit" }}>
|
||||||
|
<h3>{props.number}</h3>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const teamsListComponents = Object.keys(teamList.teams).map((item, index) => {
|
||||||
|
return (
|
||||||
|
<Box key={index}>
|
||||||
|
<TeamNumberComponent number={item} />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: "flex", flexDirection: "column", alignItems: "center" }}>
|
||||||
|
<Box sx={panel_sx}>{teamsListComponents}</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default TeamListPage;
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
|
import { useLocalDb, useRemoteDb } from "../DbContext";
|
||||||
|
import { Formik, FastField, Form, useFormikContext } from "formik";
|
||||||
|
import { TextField, Button, Grid, FormRow, Divider, Checkbox, Radio, FormControlLabel, FormControl, FormLabel, RadioGroup, IconButton, NotesAdornment, Box } from "@mui/material";
|
||||||
|
import { useProcessedDataBucket } from "../ProcessedDataBucketContext";
|
||||||
|
import { getProcessedDataBucket, updateProcessedDataBucket } from "../ProcessedDataBucket";
|
||||||
|
import { useLocation } from "react-router-dom";
|
||||||
|
|
||||||
|
const TeamPage = () => {
|
||||||
|
let { localdb, setLocaldb } = useLocalDb();
|
||||||
|
let { remotedb } = useRemoteDb();
|
||||||
|
const location = useLocation();
|
||||||
|
const { team } = location.state;
|
||||||
|
console.log(team);
|
||||||
|
|
||||||
|
let panel_sx = {
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: { xs: "column", md: "row" },
|
||||||
|
alignItems: { xs: "center", sm: "center" },
|
||||||
|
justifyContent: { xs: "flex-start", sm: "center" },
|
||||||
|
bgcolor: "background.paper",
|
||||||
|
p: 2,
|
||||||
|
m: 1,
|
||||||
|
gap: 2,
|
||||||
|
maxWidth: "fit-content",
|
||||||
|
borderRadius: "10px",
|
||||||
|
boxShadow: 7,
|
||||||
|
};
|
||||||
|
|
||||||
|
const [oldDoc, setOldDoc] = useState(null);
|
||||||
|
const onSubmit = useCallback(
|
||||||
|
// (old_doc, new_doc) => {
|
||||||
|
(values, { setSubmitting, resetForm }) => {
|
||||||
|
localdb.on("update", console.log);
|
||||||
|
localdb
|
||||||
|
.saveChanges(oldDoc, values)
|
||||||
|
.then((result) => {
|
||||||
|
alert("Saved Successfully!");
|
||||||
|
setSubmitting(false);
|
||||||
|
})
|
||||||
|
.then(localdb.sync(remotedb))
|
||||||
|
.catch(console.log);
|
||||||
|
},
|
||||||
|
[localdb, oldDoc]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
localdb.all().then((res) => {
|
||||||
|
let old_doc = {
|
||||||
|
$id: team,
|
||||||
|
weight: "",
|
||||||
|
drive_train: "",
|
||||||
|
drive_motors: "",
|
||||||
|
wheels: "",
|
||||||
|
climb_level: "",
|
||||||
|
misc_design: "",
|
||||||
|
};
|
||||||
|
if (typeof res[team] !== "undefined") {
|
||||||
|
old_doc = { ...res[team] };
|
||||||
|
}
|
||||||
|
setOldDoc(old_doc);
|
||||||
|
});
|
||||||
|
}, [setOldDoc]);
|
||||||
|
|
||||||
|
if (oldDoc == null) return null;
|
||||||
|
console.log(oldDoc);
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Formik initialValues={oldDoc} onSubmit={onSubmit}>
|
||||||
|
{({ isSubmitting }) => (
|
||||||
|
<Form>
|
||||||
|
<Box sx={panel_sx}>
|
||||||
|
<FastField type="input" as={TextField} name="weight" label="Weight" />
|
||||||
|
<FastField type="input" as={TextField} name="drive_train" label="Drive Train Type" />
|
||||||
|
<FastField type="input" as={TextField} name="drive_motors" label="# of Drive Motors" />
|
||||||
|
<FastField type="input" as={TextField} name="wheels" label="Wheels" />
|
||||||
|
<FastField type="input" as={TextField} name="climb_level" label="Climb Level" />
|
||||||
|
<FastField type="input" as={TextField} name="misc_design" label="Misc" />
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ ...panel_sx, display: "flex", flexDirection: "column" }}>
|
||||||
|
<Button type="submit" disabled={isSubmitting}>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Form>
|
||||||
|
)}
|
||||||
|
</Formik>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TeamPage;
|
||||||
@@ -1,87 +1,100 @@
|
|||||||
|
const processSchedule = (doc) => {};
|
||||||
|
|
||||||
|
const createDefaultTeamData = (doc, teamData) => {
|
||||||
|
//if there's no processed data on a team yet, create a default data entry
|
||||||
|
if (typeof teamData[doc.team_number] === "undefined") {
|
||||||
|
teamData[doc.team_number] = {
|
||||||
|
team_number: doc.team_number,
|
||||||
|
matches_played: 0,
|
||||||
|
notes: [],
|
||||||
|
data_sets: {
|
||||||
|
upper_hub_auto: [],
|
||||||
|
lower_hub_auto: [],
|
||||||
|
upper_hub_teleop: [],
|
||||||
|
lower_hub_teleop: [],
|
||||||
|
auto_points: [],
|
||||||
|
teleop_hub_points: [],
|
||||||
|
climb_points: [],
|
||||||
|
total_match_points: [],
|
||||||
|
},
|
||||||
|
climb_counts: [0, 0, 0, 0, 0],
|
||||||
|
average_auto_points: 0,
|
||||||
|
average_teleop_hub_points: 0,
|
||||||
|
average_climb_points: 0,
|
||||||
|
average_total_match_points: 0,
|
||||||
|
num_disables: 0,
|
||||||
|
num_flips: 0,
|
||||||
|
fouls: 0,
|
||||||
|
fouls_tech: 0,
|
||||||
|
red_cards: 0,
|
||||||
|
yellow_cards: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const processMatch = (doc, thisTeamData) => {
|
||||||
|
console.log("MATCH: " + doc._id);
|
||||||
|
//add this game's data to the respective team data:
|
||||||
|
thisTeamData.matches_played++;
|
||||||
|
|
||||||
|
let auto_points = (parseInt(doc.taxi_auto) ? 2 : 0) + parseInt(doc.upper_hub_auto) * 4 + parseInt(doc.lower_hub_auto) * 2;
|
||||||
|
let teleop_hub_points = parseInt(doc.upper_hub_teleop) * 2 + parseInt(doc.lower_hub_teleop) * 1;
|
||||||
|
let climb_points = (parseInt(doc.climb_level) == 0 ? 4 : 0) + (parseInt(doc.climb_level) == 1 ? 6 : 0) + (parseInt(doc.climb_level) == 2 ? 10 : 0) + (parseInt(doc.climb_level) == 3 ? 15 : 0);
|
||||||
|
let total_match_points = auto_points + teleop_hub_points + climb_points;
|
||||||
|
//data sets
|
||||||
|
thisTeamData.data_sets.upper_hub_auto.push(parseInt(doc.upper_hub_auto));
|
||||||
|
thisTeamData.data_sets.lower_hub_auto.push(parseInt(doc.lower_hub_auto));
|
||||||
|
thisTeamData.data_sets.upper_hub_teleop.push(parseInt(doc.upper_hub_teleop));
|
||||||
|
thisTeamData.data_sets.lower_hub_teleop.push(parseInt(doc.lower_hub_teleop));
|
||||||
|
thisTeamData.data_sets.auto_points.push(auto_points);
|
||||||
|
thisTeamData.data_sets.teleop_hub_points.push(teleop_hub_points);
|
||||||
|
thisTeamData.data_sets.climb_points.push(climb_points);
|
||||||
|
thisTeamData.data_sets.total_match_points.push(total_match_points);
|
||||||
|
|
||||||
|
//climb data
|
||||||
|
thisTeamData.climb_counts[parseInt(doc.climb_level)]++;
|
||||||
|
|
||||||
|
//misc data
|
||||||
|
thisTeamData.num_disables += doc.disabled ? 1 : 0;
|
||||||
|
thisTeamData.num_flips += doc.flipped ? 1 : 0;
|
||||||
|
thisTeamData.fouls += parseInt(doc.fouls);
|
||||||
|
thisTeamData.fouls_tech += parseInt(doc.fouls_tech);
|
||||||
|
thisTeamData.red_cards += parseInt(doc.red_cards);
|
||||||
|
thisTeamData.yellow_cards += parseInt(doc.yellow_cards);
|
||||||
|
|
||||||
|
//sum of all points in the match points data set for this team
|
||||||
|
//function for getting the sum of an array, use in reduce function of array
|
||||||
|
const sum = (accum, current) => accum + current;
|
||||||
|
thisTeamData.average_auto_points = thisTeamData.data_sets.auto_points.reduce(sum, 0) / thisTeamData.matches_played;
|
||||||
|
thisTeamData.average_teleop_hub_points = thisTeamData.data_sets.teleop_hub_points.reduce(sum, 0) / thisTeamData.matches_played;
|
||||||
|
thisTeamData.average_climb_points = thisTeamData.data_sets.climb_points.reduce(sum, 0) / thisTeamData.matches_played;
|
||||||
|
thisTeamData.average_total_match_points = thisTeamData.data_sets.total_match_points.reduce(sum, 0) / thisTeamData.matches_played;
|
||||||
|
};
|
||||||
|
|
||||||
export function updateProcessedDataBucket(db, setProcessedDataBucket) {
|
export function updateProcessedDataBucket(db, setProcessedDataBucket) {
|
||||||
|
console.log("updating pdb");
|
||||||
return db
|
return db
|
||||||
.allDocs({
|
.all()
|
||||||
include_docs: true,
|
|
||||||
})
|
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
|
// console.log("TESTH");
|
||||||
|
// console.log(result);
|
||||||
//reset data
|
//reset data
|
||||||
let teamData = {};
|
let teamData = {};
|
||||||
let matchData = {};
|
let matchData = {};
|
||||||
|
|
||||||
result.rows.forEach((dbentry) => {
|
Object.values(result).forEach((doc) => {
|
||||||
let doc = dbentry.doc;
|
// console.log(doc);
|
||||||
|
if (doc.type === "schedule") {
|
||||||
//if there's no processed data on a team yet, create a default data entry
|
return;
|
||||||
if (typeof teamData[doc.team_number] === "undefined") {
|
|
||||||
teamData[doc.team_number] = {
|
|
||||||
team_number: doc.team_number,
|
|
||||||
matches_played: 0,
|
|
||||||
notes: [],
|
|
||||||
data_sets: {
|
|
||||||
upper_hub_auto: [],
|
|
||||||
lower_hub_auto: [],
|
|
||||||
upper_hub_teleop: [],
|
|
||||||
lower_hub_teleop: [],
|
|
||||||
auto_points: [],
|
|
||||||
teleop_hub_points: [],
|
|
||||||
climb_points: [],
|
|
||||||
total_match_points: [],
|
|
||||||
},
|
|
||||||
climb_counts: [0, 0, 0, 0, 0],
|
|
||||||
average_auto_points: 0,
|
|
||||||
average_teleop_hub_points: 0,
|
|
||||||
average_climb_points: 0,
|
|
||||||
average_total_match_points: 0,
|
|
||||||
num_disables: 0,
|
|
||||||
num_flips: 0,
|
|
||||||
fouls: 0,
|
|
||||||
fouls_tech: 0,
|
|
||||||
red_cards: 0,
|
|
||||||
yellow_cards: 0,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let thisTeamData = teamData[doc.team_number];
|
let thisTeamData = teamData[doc.team_number];
|
||||||
if (doc.type === "match")
|
if (doc.type === "match") {
|
||||||
{
|
createDefaultTeamData(doc, teamData);
|
||||||
console.log("MATCH: " + doc._id)
|
processMatch(doc, thisTeamData);
|
||||||
//add this game's data to the respective team data:
|
}
|
||||||
thisTeamData.matches_played++;
|
if (doc.type === "notes") {
|
||||||
|
createDefaultTeamData(doc, teamData);
|
||||||
let auto_points = (parseInt(doc.taxi_auto) ? 2 : 0) + parseInt(doc.upper_hub_auto) * 4 + parseInt(doc.lower_hub_auto) * 2;
|
|
||||||
let teleop_hub_points = parseInt(doc.upper_hub_teleop) * 2 + parseInt(doc.lower_hub_teleop) * 1;
|
|
||||||
let climb_points = (parseInt(doc.climb_level) == 0 ? 4 : 0) + (parseInt(doc.climb_level) == 1 ? 6 : 0) + (parseInt(doc.climb_level) == 2 ? 10 : 0) + (parseInt(doc.climb_level) == 3 ? 15 : 0);
|
|
||||||
let total_match_points = auto_points + teleop_hub_points + climb_points;
|
|
||||||
//data sets
|
|
||||||
thisTeamData.data_sets.upper_hub_auto.push(parseInt(doc.upper_hub_auto));
|
|
||||||
thisTeamData.data_sets.lower_hub_auto.push(parseInt(doc.lower_hub_auto));
|
|
||||||
thisTeamData.data_sets.upper_hub_teleop.push(parseInt(doc.upper_hub_teleop));
|
|
||||||
thisTeamData.data_sets.lower_hub_teleop.push(parseInt(doc.lower_hub_teleop));
|
|
||||||
thisTeamData.data_sets.auto_points.push(auto_points);
|
|
||||||
thisTeamData.data_sets.teleop_hub_points.push(teleop_hub_points);
|
|
||||||
thisTeamData.data_sets.climb_points.push(climb_points);
|
|
||||||
thisTeamData.data_sets.total_match_points.push(total_match_points);
|
|
||||||
|
|
||||||
//climb data
|
|
||||||
thisTeamData.climb_counts[parseInt(doc.climb_level)]++;
|
|
||||||
|
|
||||||
//misc data
|
|
||||||
thisTeamData.num_disables += doc.disabled ? 1 : 0;
|
|
||||||
thisTeamData.num_flips += doc.flipped ? 1 : 0;
|
|
||||||
thisTeamData.fouls += parseInt(doc.fouls);
|
|
||||||
thisTeamData.fouls_tech += parseInt(doc.fouls_tech);
|
|
||||||
thisTeamData.red_cards += parseInt(doc.red_cards);
|
|
||||||
thisTeamData.yellow_cards += parseInt(doc.yellow_cards);
|
|
||||||
|
|
||||||
//sum of all points in the match points data set for this team
|
|
||||||
//function for getting the sum of an array, use in reduce function of array
|
|
||||||
const sum = (accum, current) => accum + current;
|
|
||||||
thisTeamData.average_auto_points = thisTeamData.data_sets.auto_points.reduce(sum, 0) / thisTeamData.matches_played;
|
|
||||||
thisTeamData.average_teleop_hub_points = thisTeamData.data_sets.teleop_hub_points.reduce(sum, 0) / thisTeamData.matches_played;
|
|
||||||
thisTeamData.average_climb_points = thisTeamData.data_sets.climb_points.reduce(sum, 0) / thisTeamData.matches_played;
|
|
||||||
thisTeamData.average_total_match_points = thisTeamData.data_sets.total_match_points.reduce(sum, 0) / thisTeamData.matches_played;
|
|
||||||
} else if (doc.type === "notes") {
|
|
||||||
console.log("NOTES: " + doc._id)
|
|
||||||
thisTeamData.notes.push(doc.notes);
|
thisTeamData.notes.push(doc.notes);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import React, { useContext, useState } from "react";
|
import React, { useContext, useState } from "react";
|
||||||
|
import { useLocalDb } from "./DbContext.jsx";
|
||||||
import { ProcessedDataBucket } from "./ProcessedDataBucket.jsx";
|
import { ProcessedDataBucket } from "./ProcessedDataBucket.jsx";
|
||||||
|
import { getProcessedDataBucket, updateProcessedDataBucket } from "./ProcessedDataBucket";
|
||||||
|
|
||||||
export const ProcessedDataBucketContext = React.createContext();
|
export const ProcessedDataBucketContext = React.createContext();
|
||||||
export function useProcessedDataBucket() {
|
export function useProcessedDataBucket() {
|
||||||
@@ -8,6 +10,10 @@ export function useProcessedDataBucket() {
|
|||||||
|
|
||||||
export function ProcessedDataBucketProvider({ children }) {
|
export function ProcessedDataBucketProvider({ children }) {
|
||||||
//create the processed data bucket object
|
//create the processed data bucket object
|
||||||
|
const { localdb } = useLocalDb();
|
||||||
const [processedDataBucket, setProcessedDataBucket] = useState(null);
|
const [processedDataBucket, setProcessedDataBucket] = useState(null);
|
||||||
|
localdb.on("change", (change) => {
|
||||||
|
updateProcessedDataBucket(localdb, setProcessedDataBucket);
|
||||||
|
});
|
||||||
return <ProcessedDataBucketContext.Provider value={{ processedDataBucket, setProcessedDataBucket }}>{children}</ProcessedDataBucketContext.Provider>;
|
return <ProcessedDataBucketContext.Provider value={{ processedDataBucket, setProcessedDataBucket }}>{children}</ProcessedDataBucketContext.Provider>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,11 +9,11 @@ const DbChooser = (props) => {
|
|||||||
const { processedDataBucket, setProcessedDataBucket } = useProcessedDataBucket();
|
const { processedDataBucket, setProcessedDataBucket } = useProcessedDataBucket();
|
||||||
|
|
||||||
const [dbname, setDbName] = React.useState(localdb.name);
|
const [dbname, setDbName] = React.useState(localdb.name);
|
||||||
|
|
||||||
const sync = useCallback(()=>{
|
const sync = useCallback(() => {
|
||||||
localdb.sync(remotedb, {
|
localdb.sync(remotedb, {
|
||||||
// live:true,
|
// live:true,
|
||||||
retry:true
|
retry: true,
|
||||||
});
|
});
|
||||||
}, [localdb, remotedb]);
|
}, [localdb, remotedb]);
|
||||||
|
|
||||||
@@ -25,21 +25,19 @@ const DbChooser = (props) => {
|
|||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Box sx={{ width: 400 }}>
|
{/* <Box sx={{ width: 400 }}>
|
||||||
<FormControl fullWidth>
|
<FormControl fullWidth>
|
||||||
<InputLabel>Database</InputLabel>
|
<InputLabel>Database</InputLabel>
|
||||||
<Select value={dbname} label="Database Name" onChange={handleChange}>
|
<Select value={dbname} label="Database Name" onChange={handleChange}>
|
||||||
<MenuItem value={"denver_practice"}>Denver Practice Matches</MenuItem>
|
<MenuItem value={"denver_practice"}>Denver Practice Matches</MenuItem>
|
||||||
<MenuItem value={"denver_fr"}>Denver For Real</MenuItem>
|
<MenuItem value={"denver_fr"}>Denver For Real</MenuItem>
|
||||||
{/* <MenuItem value={"utah_practice"}>Utah Practice Matches</MenuItem>
|
<MenuItem value={"utah_practice"}>Utah Practice Matches</MenuItem>
|
||||||
<MenuItem value={"utah_fr"}>Utah For Real</MenuItem> */}
|
<MenuItem value={"utah_fr"}>Utah For Real</MenuItem>
|
||||||
<MenuItem value={"testdata"}>Test Data</MenuItem>
|
<MenuItem value={"testdata"}>Test Data</MenuItem>
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<Button onClick={sync}>
|
<Button onClick={sync}>Force Sync</Button>
|
||||||
Force Sync
|
</Box> */}
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ const PagesList = props => {
|
|||||||
<ul>
|
<ul>
|
||||||
<li><Link onClick={props.click} to="/Dashboard">Dashboard</Link></li>
|
<li><Link onClick={props.click} to="/Dashboard">Dashboard</Link></li>
|
||||||
<li><Link onClick={props.click} to="/Input">Input</Link></li>
|
<li><Link onClick={props.click} to="/Input">Input</Link></li>
|
||||||
<li><Link onClick={props.click} to="/Notes">Notes</Link></li>
|
{/* <li><Link onClick={props.click} to="/Notes">Notes</Link></li> */}
|
||||||
|
<li><Link onClick={props.click} to="/TeamList">Teams</Link></li>
|
||||||
|
<li><Link onClick={props.click} to="/Schedule">Schedule</Link></li>
|
||||||
</ul>
|
</ul>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
import React, { useState, useCallback, useEffect } from "react";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import { Formik, FastField, Form, useFormikContext } from "formik";
|
||||||
|
import { TextField, Button, Grid, FormRow, Divider, Checkbox, Radio, FormControlLabel, FormControl, FormLabel, RadioGroup, IconButton, NotesAdornment, Box } from "@mui/material";
|
||||||
|
import { useLocalDb, useRemoteDb } from "../DbContext";
|
||||||
|
import { useProcessedDataBucket } from "../ProcessedDataBucketContext";
|
||||||
|
|
||||||
|
const Schedule = (props) => {
|
||||||
|
let { localdb, setLocaldb } = useLocalDb();
|
||||||
|
// let
|
||||||
|
|
||||||
|
let panel_sx = {
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: { xs: "center", sm: "center" },
|
||||||
|
justifyContent: { xs: "flex-start", sm: "center" },
|
||||||
|
bgcolor: "background.paper",
|
||||||
|
p: 2,
|
||||||
|
m: 1,
|
||||||
|
gap: 2,
|
||||||
|
maxWidth: "fit-content",
|
||||||
|
borderRadius: "10px",
|
||||||
|
boxShadow: 7,
|
||||||
|
};
|
||||||
|
const [schedule, setSchedule] = useState(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
localdb.all().then((res) => {
|
||||||
|
// console.log(res["schedule"]);
|
||||||
|
setSchedule(res["schedule"]);
|
||||||
|
});
|
||||||
|
}, [setSchedule]);
|
||||||
|
if (schedule == null) return <div />;
|
||||||
|
|
||||||
|
const TeamNumberComponent = (props) => {
|
||||||
|
return (
|
||||||
|
<Link to="/Team" state={{ team: props.number }} style={{ color: "inherit" }}>
|
||||||
|
<h3>{props.number}</h3>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const scheduleList = schedule.matches.map((item, index) => {
|
||||||
|
// console.log(item);
|
||||||
|
return (
|
||||||
|
<Box sx={panel_sx} key={index}>
|
||||||
|
<h3>{item.match_id}</h3>
|
||||||
|
<Box sx={{ display: "flex", flexDirection: { xs: "column", sm: "row" }, gap: { xs: 0, sm: 1.5 }, color: "red_alliance", textDecoration: "none" }}>
|
||||||
|
<TeamNumberComponent number={item.red[0]} />
|
||||||
|
<TeamNumberComponent number={item.red[1]} />
|
||||||
|
<TeamNumberComponent number={item.red[2]} />
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ display: "flex", flexDirection: { xs: "column", sm: "row" }, gap: { xs: 0, sm: 1.5 }, color: "blue_alliance" }}>
|
||||||
|
{/* <li> */}
|
||||||
|
<TeamNumberComponent number={item.blue[0]} />
|
||||||
|
<TeamNumberComponent number={item.blue[1]} />
|
||||||
|
<TeamNumberComponent number={item.blue[2]} />
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return <Box sx={{ display: "flex", flexDirection: "column", alignItems: "center" }}>{scheduleList}</Box>;
|
||||||
|
// console.log(schedule);
|
||||||
|
|
||||||
|
// superagent
|
||||||
|
// .get("https://www.thebluealliance.com/api/v3" + "/event/2022code/matches")
|
||||||
|
// .set("X-TBA-Auth-Key", "6aXgVYCAcyy4O7FwCGLqj5ATcima5k25smssLqUuHAHTCvGtCWXX7aoM9xNWfaSm")
|
||||||
|
// .end((err, res) => {
|
||||||
|
// console.log(JSON.parse(res.text));
|
||||||
|
// });
|
||||||
|
|
||||||
|
// const { values, submitForm } = useFormikContext();
|
||||||
|
// const { processedDataBucket, setProcessedDataBucket } = useProcessedDataBucket();
|
||||||
|
// if (processedDataBucket == null) {
|
||||||
|
// return null;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // console.log(values);
|
||||||
|
// if (typeof processedDataBucket.teamData[values.team_number] === "undefined") {
|
||||||
|
// return null;
|
||||||
|
// } else {
|
||||||
|
// // let notesList = processedDataBucket.teamData[values.team_number].notes;
|
||||||
|
// let notesList = processedDataBucket.teamData[values.team_number].notes;
|
||||||
|
// // let notesComponents = notesList.map((item, index)=>{
|
||||||
|
// return notesList.map((item, index) => {
|
||||||
|
// return <h3 key={index}>{item}</h3>;
|
||||||
|
// });
|
||||||
|
// // return (
|
||||||
|
// // // <h2>{processedDataBucket.teamData[values.team_number].Name</h2>
|
||||||
|
// // {notesComponents}
|
||||||
|
// // )
|
||||||
|
// }
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Schedule;
|
||||||
Reference in New Issue
Block a user