Skip to content

Commit

Permalink
Allow coaches to add entries
Browse files Browse the repository at this point in the history
  • Loading branch information
RobertOstermann committed Nov 2, 2023
1 parent 4cb83f4 commit f8820fa
Show file tree
Hide file tree
Showing 13 changed files with 344 additions and 83 deletions.
26 changes: 0 additions & 26 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,31 +36,5 @@
"close": true
}
},
{
"label": "Database Info: maple-river-basketball",
"command": "heroku pg:info --app maple-river-basketball",
"problemMatcher": [],
"type": "shell",
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "dedicated",
"showReuseMessage": true
}
},
{
"label": "Database Info: robbie-training",
"command": "heroku pg:info --app robbie-training",
"problemMatcher": [],
"type": "shell",
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "dedicated",
"showReuseMessage": true
}
}
]
}
9 changes: 3 additions & 6 deletions client/src/api/entry/UserEntryModel.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
export default interface UserEntryModel {
id?: number;
authId?: string;
activityType?: number;
activityDate?: any;
activityDuration?: number;
import EntryModel from "./EntryModel";

export default interface UserEntryModel extends EntryModel {
firstName?: string;
lastName?: string;
}
48 changes: 48 additions & 0 deletions client/src/components/pages/coaches/Entry/Entry.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
@import "../../../../styles/colors.scss";

// Form
.form {
padding-top: 0rem;
padding-bottom: 4.5rem;
}

// Form Group
.formGroup {
padding-bottom: 2rem;
// text-align: center;
}

// DatePicker
.datePicker {
width: 100%;
text-align: center;
}

// Logout Button
.logoutDiv {
// top | horizontal | bottom
margin: 4rem 1rem 0rem;
}

.logoutButton {
color: $secondary;
width: 100%;
}

// Submit Button
.submitDiv {
// top | horizontal | bottom
margin: 0rem 1rem 5.5rem;
}

.submitButton {
color: $secondary;
width: 100%;
}

// Player Name
.button {
padding: 0rem;
margin-bottom: 1rem;
width: 100%;
}
218 changes: 218 additions & 0 deletions client/src/components/pages/coaches/Entry/Entry.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
import { useEffect, useState } from "react";
import { Button, Container, Form, Modal, Nav, Navbar } from "react-bootstrap";
import DatePicker from "react-datepicker";
import { useMutation, useQuery, useQueryClient, UseQueryOptions } from "react-query";
import { NavLink, useParams } from "react-router-dom";

import EntryRequests from "../../../../api/entry/EntryRequests";
import UserModel from "../../../../api/user/UserModel";
import UserRequests from "../../../../api/user/UserRequests";
import {
ActivityTypeInterface,
ActivityTypes,
} from "../../../../shared/constants/ActivityTypes";
import { useStoreAuthentication } from "../../../../store/authentication/AuthenticationStore";
import RouterHelper from "../../../routers/RouterHelper";

import "react-datepicker/dist/react-datepicker.css";
import styles from "./Entry.module.scss";

export default function CoachEntry() {
const { id } = useParams();
const queryClient = useQueryClient();

const [user, setUser] = useState<UserModel | undefined>();
const [isLoading, setIsLoading] = useState(false);
const [activity, setActivity] = useState<number>(ActivityTypes.game.id);
const [duration, setDuration] = useState<number>(15);
const [date, setDate] = useState<Date>(
new Date(new Date().setHours(0, 0, 0, 0))
);
const [success, setSuccess] = useState(false);
const [showModal, setShowModal] = useState(false);

const token = useStoreAuthentication((state) => state.token);

const queryOptions: UseQueryOptions<unknown, unknown, unknown, any> = {
enabled: token !== "" && id !== undefined,
refetchOnWindowFocus: false,
retry: 1,
staleTime: 15 * 60 * 1000, // 15 minutes
cacheTime: 30 * 60 * 1000, // 30 minutes
};
const queryResponse = useQuery(
[`get-user-${id}`],
() => UserRequests.getUserById(parseInt(id ?? "0"), token),
queryOptions
);

const mutation = useMutation({
mutationFn: () => EntryRequests.createEntry(token, {
authId: user?.authId,
activityType: activity,
activityDuration: duration,
activityDate: date,
}),
onSuccess: () => {
queryClient.invalidateQueries();
setSuccess(true);
},
onError: () => {
setSuccess(false);
},
onSettled: () => {
setShowModal(true);
setIsLoading(false);
}
});

useEffect(() => {
if (queryResponse.isSuccess || queryResponse.isError) {
setIsLoading(false);
}

if (queryResponse.isSuccess) {
const user: UserModel = queryResponse.data as UserModel;
if (!user) return;

setUser(user);
}
}, [queryResponse.data, queryResponse.isSuccess, queryResponse.isError]);

const submitEntry = async () => {
setIsLoading(true);
mutation.mutate();
};

const dateButton = () => {
const minimumDate = new Date();
minimumDate.setDate(minimumDate.getDate() - 7);
const maximumDate = new Date();

return (
<Container className={styles.datePicker}>
<DatePicker
inline
useWeekdaysShort
selected={date}
minDate={minimumDate}
maxDate={maximumDate}
onChange={(updatedDate: Date) =>
setDate(new Date(updatedDate.setHours(0, 0, 0, 0)))
}
/>
</Container>
);
};

const durationOptions = () => {
const options = [];
let currentDuration = 15;
const maxDuration = 180;

let index = 0;
while (currentDuration <= maxDuration) {
currentDuration += 15;
const hours = Math.floor(currentDuration / 60);
const hoursString =
hours > 0 ? (hours === 1 ? `${hours} Hour ` : `${hours} Hours `) : "";
const minutes = currentDuration % 60;
const minutesString = `${minutes} minutes`;

options.push(
<option
key={`duration-${index}`}
value={currentDuration}
>{`${hoursString}${minutesString}`}</option>
);
index++;
}

return options;
};

const submitButton = () => {
return (
<Navbar
fixed="bottom"
id="navbar"
role="navigation"
variant="dark"
className={styles.submitDiv}
>
<Nav justify className="w-100">
<Button
variant="primary"
size="lg"
onClick={isLoading ? undefined : submitEntry}
className={styles.submitButton}
>
{isLoading ? "Submitting..." : "Submit Entry"}
</Button>
</Nav>
</Navbar>
);
};

const successModal = () => {
return (
<Modal size="lg" backdrop="static" centered show={showModal}>
<Modal.Header>
<Modal.Title>{success ? "Entry Created" : "Error"}</Modal.Title>
</Modal.Header>
<Modal.Body>
<p>
{success
? "The entry was created successfully."
: "There was a problem creating the entry. Please try again."}
</p>
</Modal.Body>
<Modal.Footer>
<Container fluid className="d-grid">
<Button onClick={() => setShowModal(false)}>Close</Button>
</Container>
</Modal.Footer>
</Modal>
);
};

return (
<Container fluid>
<NavLink
end
to={`${RouterHelper.coach.players.path}/${id}`}
>
<Button className={styles.button} size="lg">
{user?.firstName} {user?.lastName}
</Button>
</NavLink>
<Form className={styles.form}>
<Form.Group className={styles.formGroup}>{dateButton()}</Form.Group>
<Form.Group className={styles.formGroup}>
<Form.Label>Activity</Form.Label>
<Form.Select
onChange={(event) => setActivity(parseInt(event.target.value))}
>
{Object.values(ActivityTypes).map(
(activityType: ActivityTypeInterface, index: number) => {
return (
<option value={activityType.id} key={`activity-${index}`}>{activityType.ui}</option>
);
}
)}
</Form.Select>
</Form.Group>
<Form.Group className={styles.formGroup}>
<Form.Label>Duration</Form.Label>
<Form.Select
onChange={(event) => setDuration(parseInt(event.target.value))}
>
{durationOptions()}
</Form.Select>
</Form.Group>
</Form>
{submitButton()}
{successModal()}
</Container>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@
text-align: center;
}

// Add Entry
.button {
padding: 0rem;
margin-bottom: 1rem;
width: 100%;
}

// Category Cards
.categoryDiv {
padding-bottom: 0rem;
Expand Down
13 changes: 11 additions & 2 deletions client/src/components/pages/coaches/Players/Player/Player.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React from "react";
import { useEffect, useState } from "react";
import { Card, Col, Container, Row } from "react-bootstrap";
import { Button, Card, Col, Container, Row } from "react-bootstrap";
import { useQuery, UseQueryOptions } from "react-query";
import { useParams } from "react-router-dom";
import { NavLink, useParams } from "react-router-dom";

import EntryModel from "../../../../../api/entry/EntryModel";
import UserModel from "../../../../../api/user/UserModel";
Expand All @@ -12,6 +12,7 @@ import {
ActivityTypes,
} from "../../../../../shared/constants/ActivityTypes";
import { useStoreAuthentication } from "../../../../../store/authentication/AuthenticationStore";
import RouterHelper from "../../../../routers/RouterHelper";
import { getDurationString } from "../../../shared/Leaders/Leaders";
import Loading from "../../../shared/Loading/Loading";

Expand Down Expand Up @@ -191,6 +192,14 @@ export default function Player() {
return (
<Container fluid>
<div className={styles.headerDiv}>{getHeader()}</div>
<NavLink
end
to={`${RouterHelper.coach.players.path}/${id}/new-entry`}
>
<Button className={styles.button} size="lg">
Add Entry
</Button>
</NavLink>
<div className={styles.categoryDiv}>
{totalCard()}
{categoryCards()}
Expand Down
Loading

0 comments on commit f8820fa

Please sign in to comment.