diff --git a/components/ui/badge.tsx b/components/ui/badge.tsx new file mode 100644 index 0000000..f000e3e --- /dev/null +++ b/components/ui/badge.tsx @@ -0,0 +1,36 @@ +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const badgeVariants = cva( + "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", + { + variants: { + variant: { + default: + "border-transparent bg-primary text-primary-foreground hover:bg-primary/80", + secondary: + "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", + destructive: + "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", + outline: "text-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +export interface BadgeProps + extends React.HTMLAttributes, + VariantProps {} + +function Badge({ className, variant, ...props }: BadgeProps) { + return ( +
+ ) +} + +export { Badge, badgeVariants } diff --git a/components/ui/label.tsx b/components/ui/label.tsx new file mode 100644 index 0000000..5341821 --- /dev/null +++ b/components/ui/label.tsx @@ -0,0 +1,26 @@ +"use client" + +import * as React from "react" +import * as LabelPrimitive from "@radix-ui/react-label" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const labelVariants = cva( + "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" +) + +const Label = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & + VariantProps +>(({ className, ...props }, ref) => ( + +)) +Label.displayName = LabelPrimitive.Root.displayName + +export { Label } diff --git a/components/ui/table.tsx b/components/ui/table.tsx new file mode 100644 index 0000000..7f3502f --- /dev/null +++ b/components/ui/table.tsx @@ -0,0 +1,117 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +const Table = React.forwardRef< + HTMLTableElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+ + +)) +Table.displayName = "Table" + +const TableHeader = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +TableHeader.displayName = "TableHeader" + +const TableBody = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +TableBody.displayName = "TableBody" + +const TableFooter = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + tr]:last:border-b-0", + className + )} + {...props} + /> +)) +TableFooter.displayName = "TableFooter" + +const TableRow = React.forwardRef< + HTMLTableRowElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +TableRow.displayName = "TableRow" + +const TableHead = React.forwardRef< + HTMLTableCellElement, + React.ThHTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +TableHead.displayName = "TableHead" + +const TableCell = React.forwardRef< + HTMLTableCellElement, + React.TdHTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +TableCell.displayName = "TableCell" + +const TableCaption = React.forwardRef< + HTMLTableCaptionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +TableCaption.displayName = "TableCaption" + +export { + Table, + TableHeader, + TableBody, + TableFooter, + TableHead, + TableRow, + TableCell, + TableCaption, +} diff --git a/package-lock.json b/package-lock.json index cae869e..951edbc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,9 +13,11 @@ "@emotion/styled": "^11.11.0", "@mui/material": "^5.16.4", "@radix-ui/react-dialog": "^1.1.1", + "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-toast": "^1.2.1", "@reduxjs/toolkit": "^1.9.6", + "@tanstack/react-query": "^5.59.0", "@trivago/prettier-plugin-sort-imports": "^4.3.0", "@types/node": "20.6.3", "@types/react": "18.2.22", @@ -42,6 +44,7 @@ "typescript": "5.2.2" }, "devDependencies": { + "@iconify/react": "^5.0.2", "@typescript-eslint/eslint-plugin": "^6.21.0", "eslint-plugin-prettier": "^5.2.1", "eslint-plugin-promise": "^6.4.0", @@ -548,6 +551,27 @@ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==" }, + "node_modules/@iconify/react": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@iconify/react/-/react-5.0.2.tgz", + "integrity": "sha512-wtmstbYlEbo4NDxFxBJkhkf9gJBDqMGr7FaqLrAUMneRV3Z+fVHLJjOhWbkAF8xDQNFC/wcTYdrWo1lnRhmagQ==", + "dev": true, + "dependencies": { + "@iconify/types": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/cyberalien" + }, + "peerDependencies": { + "react": ">=16" + } + }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "dev": true + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", @@ -1166,6 +1190,28 @@ } } }, + "node_modules/@radix-ui/react-label": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.0.tgz", + "integrity": "sha512-peLblDlFw/ngk3UWq0VnYaOLy6agTZZ+MUO/WhVfm14vJGML+xH4FAl2XQGLqdefjNb7ApRg6Yn7U42ZhmYXdw==", + "dependencies": { + "@radix-ui/react-primitive": "2.0.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-portal": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.1.tgz", @@ -1404,6 +1450,30 @@ "tslib": "^2.4.0" } }, + "node_modules/@tanstack/query-core": { + "version": "5.59.0", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.59.0.tgz", + "integrity": "sha512-WGD8uIhX6/deH/tkZqPNcRyAhDUqs729bWKoByYHSogcshXfFbppOdTER5+qY7mFvu8KEFJwT0nxr8RfPTVh0Q==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.59.0", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.59.0.tgz", + "integrity": "sha512-YDXp3OORbYR+8HNQx+lf4F73NoiCmCcSvZvgxE29OifmQFk0sBlO26NWLHpcNERo92tVk3w+JQ53/vkcRUY1hA==", + "dependencies": { + "@tanstack/query-core": "5.59.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, "node_modules/@trivago/prettier-plugin-sort-imports": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/@trivago/prettier-plugin-sort-imports/-/prettier-plugin-sort-imports-4.3.0.tgz", diff --git a/package.json b/package.json index e450ddb..bbf9a3b 100644 --- a/package.json +++ b/package.json @@ -13,9 +13,11 @@ "@emotion/styled": "^11.11.0", "@mui/material": "^5.16.4", "@radix-ui/react-dialog": "^1.1.1", + "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-toast": "^1.2.1", "@reduxjs/toolkit": "^1.9.6", + "@tanstack/react-query": "^5.59.0", "@trivago/prettier-plugin-sort-imports": "^4.3.0", "@types/node": "20.6.3", "@types/react": "18.2.22", @@ -42,6 +44,7 @@ "typescript": "5.2.2" }, "devDependencies": { + "@iconify/react": "^5.0.2", "@typescript-eslint/eslint-plugin": "^6.21.0", "eslint-plugin-prettier": "^5.2.1", "eslint-plugin-promise": "^6.4.0", diff --git a/src/app/api/jerseyBidding.js b/src/app/api/jerseyBidding.js new file mode 100644 index 0000000..79253c9 --- /dev/null +++ b/src/app/api/jerseyBidding.js @@ -0,0 +1,32 @@ +import axios from "axios"; + +axios.defaults.withCredentials = true; + +// Does a call for User's bidding info +export const getUserBiddings = async () => { + try { + const response = await axios.get(`${process.env.NEXT_PUBLIC_BACKEND_URL}/jersey/info`); + + console.log("response", response.data.data); + + if (response.data.success) { + // console.log("This is eligible bids" + JSON.stringify(response.data.data)); + return response.data.data; + } + } catch (error) { + console.error("Error during getting user bids", error); + } +}; + +export const getUserEligibleBids = async () => { + try { + const response = await axios.get(`${process.env.NEXT_PUBLIC_BACKEND_URL}/jersey/eligible`); + + if (response.data.success) { + console.log("This is eligible bids" + JSON.stringify(response.data.data)); + return response.data.data; + } + } catch (error) { + console.error("Error during getting user bids", error); + } +}; diff --git a/src/app/components/BiddingTable.tsx b/src/app/components/BiddingTable.tsx index 5f354f2..3d24aec 100644 --- a/src/app/components/BiddingTable.tsx +++ b/src/app/components/BiddingTable.tsx @@ -1,53 +1,129 @@ import React from "react"; +import { useEffect, useState } from "react"; + +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; +import { Icon } from "@iconify/react"; +import { QueryObserverResult } from "@tanstack/react-query"; +import axios from "axios"; +import { Trash2 } from "lucide-react"; -import type { Bidding } from "@/src/app/dashboard/jersey/page"; import type { ToastMessage } from "@/src/app/dashboard/jersey/page"; +import { BiddingData, EligibleBids, UserBid } from "../dashboard/jersey/types"; + interface BiddingList { - biddings: Bidding[]; - setBiddings: React.Dispatch>; - updateUser: () => void; - setToast: React.Dispatch>; - handleOpen: () => void; + userBids: UserBid; + refetchUserBids: () => Promise>; + biddings: BiddingData; + userEligibleBids: EligibleBids; + // setBiddings: React.Dispatch>; + // updateUser: () => void; + // setToast: React.Dispatch>; + // handleOpen: () => void; } -const axios = require("axios"); -const axiosWithCredentials = axios.create({ - withCredentials: true, -}); +axios.defaults.withCredentials = true; + +// User submit bid form +const BiddingTable: React.FC = ({ userBids, refetchUserBids, biddings, userEligibleBids }) => { + const [open, setOpen] = useState(false); + const [selectedNumber, setSelectedNumber] = useState(null); + const priority = userBids ? userBids.bids.length : 0; + + const curr_userBids = userBids.bids.map(bid => { + return { + number: bid.jersey.number, + priority: bid.priority, + }; + }); -const BiddingTable: React.FC = ({ biddings, setBiddings, updateUser, setToast, handleOpen }) => { - const deleteBid = (ind: number) => { - const filteredList = biddings.filter(bidding => bidding.number != biddings[ind].number); - setBiddings(filteredList); + const canBid = userBids.canBid; + + const handleOpenModal = (number: number) => { + setOpen(true); + setSelectedNumber(number); }; - // Currently unable to make the api call - const handleSubmit = async (e: React.MouseEvent) => { - e.preventDefault(); + console.log("userBids", userBids); + const handlePlaceBid = async (number: number) => { + try { + const newBids = { + bids: [ + ...curr_userBids, + { + number, + priority: curr_userBids.length + 1, + }, + ], + }; + const resp = await axios.post(`${process.env.NEXT_PUBLIC_BACKEND_URL}/jersey/bid`, newBids); + + if (resp.status === 200) { + refetchUserBids(); + } + } catch (err) { + console.log(err); + } + }; + + const handleDeleteBid = async (number: number) => { try { - const response = await axiosWithCredentials.post(`${process.env.NEXT_PUBLIC_BACKEND_URL}/bid/create`, { - bids: biddings, + const newBids = curr_userBids.filter(bid => bid.number !== number); + const resp = await axios.post(`${process.env.NEXT_PUBLIC_BACKEND_URL}/jersey/bid`, { + bids: newBids, }); - if (response.data.success) { - setToast({ message: "Bids submitted", severity: "success" }); - handleOpen(); - updateUser(); - } else { - console.error("Bids failed"); - setToast({ message: "Bids failed to be submitted", severity: "error" }); - handleOpen(); + if (resp.status == 200) { + refetchUserBids(); } - } catch (error) { - console.error("Error during form submission", error); + } catch (err) { + console.log(err); } }; + // const getEligibleNumbers = () => { + // return userEligibleBids.bids.map(bid => bid.jersey.number); + // }; + + // const eligibleNumbers = getEligibleNumbers(); + + // const deleteBid = (ind: number) => { + // const filteredList = biddings.filter(bidding => bidding.number != biddings[ind].number); + // setBiddings(filteredList); + // }; + + // Currently unable to make the api call + // const handleSubmit = async (e: React.MouseEvent) => { + // e.preventDefault(); + // try { + // const response = await axiosWithCredentials.post(`${process.env.NEXT_PUBLIC_BACKEND_URL}/bid/create`, { + // biddings: biddings, + // }); + + // if (response.data.success) { + // setToast({ message: "biddings submitted", severity: "success" }); + // handleOpen(); + // updateUser(); + // } else { + // console.error("biddings failed"); + // setToast({ message: "biddings failed to be submitted", severity: "error" }); + // handleOpen(); + // } + // } catch (error) { + // console.error("Error during form submission", error); + // } + // }; + return ( -
+ /*
-

Submit new bids:

+

Submit new biddings:

= ({ biddings, setBiddings, updateUser

Ensure you click submit to confirm changes

- {biddings.length == 0 ? ( + {/* {biddings.length == 0 ? ( <> ) : ( - )} + )} }
@@ -93,14 +169,14 @@ const BiddingTable: React.FC = ({ biddings, setBiddings, updateUser - {biddings.map((bidding, index) => ( + {Object.entries(biddings).map(([jerseyNumber, bidding], index) => ( - - + + {/* }
{index + 1}{bidding.number}{jerseyNumber}{bidding.number} @@ -109,6 +185,153 @@ const BiddingTable: React.FC = ({ biddings, setBiddings, updateUser ))}
+ +
*/ +
+

Bidding Table

+ {userBids && ( + + + Your Bids + View and manage your current bids + + +
+
+
+

Status: {canBid ? "Can Bid" : "Cannot Bid"}

+
+
+

Points:

+

{userBids.info.points.toLocaleString()}

+
+
+

Teams :

+
+ {userBids.info.teams.map(team => ( + + {team.team.name} + + ))} +
+
+

Bids :

+
+ + + + Bidding Number + Priority + Actions + + + + {userBids.bids.map((bid, index) => ( + + {bid.jersey.number} + {bid.priority + 1} + +
+ +
+
+
+ ))} +
+
+
+
+
+
+ )} + +

Eligible Bidding Numbers :

+ +
+ {Array.from({ length: 100 }, (_, i) => i + 1).map(number => { + const isEligible = userEligibleBids !== undefined ? userEligibleBids.jerseys.includes(number) : false; // Check if the number is eligible + return ( + + ); + })} +
+ + + + + Biddings for Number {selectedNumber} + +

Your Points : {userBids.info.points}

+ {selectedNumber && biddings && biddings[selectedNumber] && ( +
+
Quota M: {biddings[selectedNumber].quota.male}
+
Quota F: {biddings[selectedNumber].quota.female}
+
+ )} +
+ + + + Room Number + User + + + + {selectedNumber && + biddings[selectedNumber] && + biddings[selectedNumber].male.map((bid, index) => ( + + {bid.user} (M) + ${bid.amount.toFixed(2)} + + ))} + {selectedNumber && + biddings[selectedNumber] && + biddings[selectedNumber].female.map((bid, index) => { + return ( + + {bid.user.room} (F) + {bid.points} + + ); + })} + + {/* {(!selectedNumber || !biddings[selectedNumber] || bids[selectedNumber].length === 0) && ( + + + No biddings yet + + + )} */} + +
+
+ + + +
+
); }; diff --git a/src/app/components/Modal/modal.tsx b/src/app/components/Modal/modal.tsx index ff0c2ad..637fdf6 100644 --- a/src/app/components/Modal/modal.tsx +++ b/src/app/components/Modal/modal.tsx @@ -1,187 +1,187 @@ -import React, { useEffect, useState } from "react"; - -import type { Bidding, ToastMessage } from "@/src/app/dashboard/jersey/page"; - -interface ModalProps { - closeModal: () => void; - index: number; - points: number; - biddings: Bidding[]; - setBiddings: React.Dispatch>; - setToast: React.Dispatch>; - handleOpen: () => void; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - currentList: any[]; -} - -interface User { - username: string; - points: number; -} - -const axios = require("axios"); -axios.defaults.withCredentials = true; - -const Modal: React.FC = ({ - closeModal, - index, - points, - biddings, - setBiddings, - setToast, - handleOpen, - // currentList, -}) => { - const [bidders, setBidders] = useState(null); - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - // const compareLists = (list: any[]): boolean => { - // for (let i = 0; i < currentList.length; i++) { - // for (let j = 0; j < list.length; j++) { - // if (currentList[i].name == list[j].name) { - // return true; - // } - // } - // } - // return false; - // }; - - // // eslint-disable-next-line @typescript-eslint/no-explicit-any - // const getNames = (list: any[]): string => { - // const names: string[] = []; - // for (let j = 0; j < list.length; j++) { - // names.push(list[j].name); - // } - - // return names.join(", "); - // }; - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const fetchList: any = async () => { - try { - const response = await axios.get(`${process.env.NEXT_PUBLIC_BACKEND_URL}/jersey/info`); - - if (response.data.success) { - await setBidders(response.data.data[index]); - } - } catch (error) { - console.error("Error during update", error); - } - }; - - useEffect(() => { - fetchList(index); - }); - - const createBid = (ind: number) => { - const duplicateArr = biddings.filter(bidding => bidding.number == ind); - - if (duplicateArr.length !== 0) { - // Include a popup to tell user to not bid for duplicates - setToast({ message: "Cannot bid for duplicate numbers", severity: "error" }); - handleOpen(); - return; - } - - if (biddings.length > 4) { - //number is 4 because when open modal for 5th number, will record 4 numbers in bidding - // Include a popup to tell user not to bid for more than 5 numbers - setToast({ message: "Cannot bid for more than 5 numbers", severity: "error" }); - handleOpen(); - return; - } - - const newBidding: Bidding = { - number: ind, - }; - - setBiddings([...biddings, newBidding]); - }; - - return ( -
-
-
-
-

Bidding List

- -
- {bidders != null && ( -
-
-
Male Quota: {bidders.quota.Male}
-
Female Quota: {bidders.quota.Female}
-
- {bidders.Male.length > 0 || bidders.Female.length > 0 ? ( - - - - - - - - - - {Object.keys(bidders) - .filter(category => category == "Male" || category == "Female") - .map( - (category, index: number) => - bidders[category].length > 0 && ( - <> - - - - - - {bidders[category].map((item: User, subIndex: number) => ( - - - - - {/* */} - - ))} - - ), - )} - -
CategoryNamePointsSports
{category}
{item.username}{item.points}{getNames(item.teams)}
- ) : ( -

No Bids Found!!!

- )} -
- )} -
-

Current Points : {points}

- -
-
-
- ); -}; - -export default Modal; +// import React, { useEffect, useState } from "react"; + +// import type { Bidding, ToastMessage } from "@/src/app/dashboard/jersey/page"; + +// interface ModalProps { +// closeModal: () => void; +// index: number; +// points: number; +// biddings: Bidding[]; +// setBiddings: React.Dispatch>; +// setToast: React.Dispatch>; +// handleOpen: () => void; +// // eslint-disable-next-line @typescript-eslint/no-explicit-any +// currentList: any[]; +// } + +// interface User { +// username: string; +// points: number; +// } + +// const axios = require("axios"); +// axios.defaults.withCredentials = true; + +// const Modal: React.FC = ({ +// closeModal, +// index, +// points, +// biddings, +// setBiddings, +// setToast, +// handleOpen, +// // currentList, +// }) => { +// const [bidders, setBidders] = useState(null); + +// // eslint-disable-next-line @typescript-eslint/no-explicit-any +// // const compareLists = (list: any[]): boolean => { +// // for (let i = 0; i < currentList.length; i++) { +// // for (let j = 0; j < list.length; j++) { +// // if (currentList[i].name == list[j].name) { +// // return true; +// // } +// // } +// // } +// // return false; +// // }; + +// // // eslint-disable-next-line @typescript-eslint/no-explicit-any +// // const getNames = (list: any[]): string => { +// // const names: string[] = []; +// // for (let j = 0; j < list.length; j++) { +// // names.push(list[j].name); +// // } + +// // return names.join(", "); +// // }; + +// // eslint-disable-next-line @typescript-eslint/no-explicit-any +// const fetchList: any = async () => { +// try { +// const response = await axios.get(`${process.env.NEXT_PUBLIC_BACKEND_URL}/jersey/info`); + +// if (response.data.success) { +// await setBidders(response.data.data[index]); +// } +// } catch (error) { +// console.error("Error during update", error); +// } +// }; + +// useEffect(() => { +// fetchList(index); +// }); + +// const createBid = (ind: number) => { +// const duplicateArr = biddings.filter(bidding => bidding.number == ind); + +// if (duplicateArr.length !== 0) { +// // Include a popup to tell user to not bid for duplicates +// setToast({ message: "Cannot bid for duplicate numbers", severity: "error" }); +// handleOpen(); +// return; +// } + +// if (biddings.length > 4) { +// //number is 4 because when open modal for 5th number, will record 4 numbers in bidding +// // Include a popup to tell user not to bid for more than 5 numbers +// setToast({ message: "Cannot bid for more than 5 numbers", severity: "error" }); +// handleOpen(); +// return; +// } + +// const newBidding: Bidding = { +// number: ind, +// }; + +// setBiddings([...biddings, newBidding]); +// }; + +// return ( +//
+//
+//
+//
+//

Bidding List

+// +//
+// {bidders != null && ( +//
+//
+//
Male Quota: {bidders.quota.Male}
+//
Female Quota: {bidders.quota.Female}
+//
+// {bidders.Male.length > 0 || bidders.Female.length > 0 ? ( +// +// +// +// +// +// +// +// + +// {Object.keys(bidders) +// .filter(category => category == "Male" || category == "Female") +// .map( +// (category, index: number) => +// bidders[category].length > 0 && ( +// <> +// +// +// +// +// +// {bidders[category].map((item: User, subIndex: number) => ( +// +// +// +// +// {/* */} +// +// ))} +// +// ), +// )} +// +//
CategoryNamePointsSports
{category}
{item.username}{item.points}{getNames(item.teams)}
+// ) : ( +//

No Bids Found!!!

+// )} +//
+// )} +//
+//

Current Points : {points}

+// +//
+//
+//
+// ); +// }; + +// export default Modal; diff --git a/src/app/components/Modal/roomModal.tsx b/src/app/components/Modal/roomModal.tsx index 68199f0..4730cf2 100644 --- a/src/app/components/Modal/roomModal.tsx +++ b/src/app/components/Modal/roomModal.tsx @@ -1,189 +1,189 @@ -import React, { useEffect, useState } from "react"; - -import type { Bidding, ToastMessage } from "@/src/app/dashboard/jersey/page"; - -interface ModalProps { - closeModal: () => void; - index: number; - points: number; - biddings: Bidding[]; - setBiddings: React.Dispatch>; - setToast: React.Dispatch>; - handleOpen: () => void; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - currentList: any[]; -} - -interface User { - username: string; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - teams: any[]; - points: number; -} - -const axios = require("axios"); -axios.defaults.withCredentials = true; - -const Modal: React.FC = ({ - closeModal, - index, - points, - biddings, - setBiddings, - setToast, - handleOpen, - currentList, -}) => { - const [bidders, setBidders] = useState(null); - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const compareLists = (list: any[]): boolean => { - for (let i = 0; i < currentList.length; i++) { - for (let j = 0; j < list.length; j++) { - if (currentList[i].name == list[j].name) { - return true; - } - } - } - return false; - }; - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const getNames = (list: any[]): string => { - const names: string[] = []; - for (let j = 0; j < list.length; j++) { - names.push(list[j].name); - } - - return names.join(", "); - }; - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const fetchList: any = async () => { - try { - const response = await axios.get(`${process.env.NEXT_PUBLIC_BACKEND_URL}/jersey/info`); - - if (response.data.success) { - await setBidders(response.data.data[index]); - } - } catch (error) { - console.error("Error during update", error); - } - }; - - useEffect(() => { - fetchList(index); - }); - - const createBid = (ind: number) => { - const duplicateArr = biddings.filter(bidding => bidding.number == ind); - - if (duplicateArr.length !== 0) { - // Include a popup to tell user to not bid for duplicates - setToast({ message: "Cannot bid for duplicate numbers", severity: "error" }); - handleOpen(); - return; - } - - if (biddings.length > 4) { - //number is 4 because when open modal for 5th number, will record 4 numbers in bidding - // Include a popup to tell user not to bid for more than 5 numbers - setToast({ message: "Cannot bid for more than 5 numbers", severity: "error" }); - handleOpen(); - return; - } - - const newBidding: Bidding = { - number: ind, - }; - - setBiddings([...biddings, newBidding]); - }; - - return ( -
-
-
-
-

Bidding List

- -
- {bidders != null && ( -
-
-
Male Quota: {bidders.quota.Male}
-
Female Quota: {bidders.quota.Female}
-
- {bidders.Male.length > 0 || bidders.Female.length > 0 ? ( - - - - - - - - - - {Object.keys(bidders) - .filter(category => category == "Male" || category == "Female") - .map( - (category, index: number) => - bidders[category].length > 0 && ( - <> - - - - - - {bidders[category].map((item: User, subIndex: number) => ( - - - - - - - ))} - - ), - )} - -
CategoryNamePointsSports
{category}
{item.username}{item.points}{getNames(item.teams)}
- ) : ( -

No Bids Found!!!

- )} -
- )} -
-

Current Points : {points}

- -
-
-
- ); -}; - -export default Modal; +// import React, { useEffect, useState } from "react"; + +// import type { Bidding, ToastMessage } from "@/src/app/dashboard/jersey/page"; + +// interface ModalProps { +// closeModal: () => void; +// index: number; +// points: number; +// biddings: Bidding[]; +// setBiddings: React.Dispatch>; +// setToast: React.Dispatch>; +// handleOpen: () => void; +// // eslint-disable-next-line @typescript-eslint/no-explicit-any +// currentList: any[]; +// } + +// interface User { +// username: string; +// // eslint-disable-next-line @typescript-eslint/no-explicit-any +// teams: any[]; +// points: number; +// } + +// const axios = require("axios"); +// axios.defaults.withCredentials = true; + +// const Modal: React.FC = ({ +// closeModal, +// index, +// points, +// biddings, +// setBiddings, +// setToast, +// handleOpen, +// currentList, +// }) => { +// const [bidders, setBidders] = useState(null); + +// // eslint-disable-next-line @typescript-eslint/no-explicit-any +// const compareLists = (list: any[]): boolean => { +// for (let i = 0; i < currentList.length; i++) { +// for (let j = 0; j < list.length; j++) { +// if (currentList[i].name == list[j].name) { +// return true; +// } +// } +// } +// return false; +// }; + +// // eslint-disable-next-line @typescript-eslint/no-explicit-any +// const getNames = (list: any[]): string => { +// const names: string[] = []; +// for (let j = 0; j < list.length; j++) { +// names.push(list[j].name); +// } + +// return names.join(", "); +// }; + +// // eslint-disable-next-line @typescript-eslint/no-explicit-any +// const fetchList: any = async () => { +// try { +// const response = await axios.get(`${process.env.NEXT_PUBLIC_BACKEND_URL}/jersey/info`); + +// if (response.data.success) { +// await setBidders(response.data.data[index]); +// } +// } catch (error) { +// console.error("Error during update", error); +// } +// }; + +// useEffect(() => { +// fetchList(index); +// }); + +// const createBid = (ind: number) => { +// const duplicateArr = biddings.filter(bidding => bidding.number == ind); + +// if (duplicateArr.length !== 0) { +// // Include a popup to tell user to not bid for duplicates +// setToast({ message: "Cannot bid for duplicate numbers", severity: "error" }); +// handleOpen(); +// return; +// } + +// if (biddings.length > 4) { +// //number is 4 because when open modal for 5th number, will record 4 numbers in bidding +// // Include a popup to tell user not to bid for more than 5 numbers +// setToast({ message: "Cannot bid for more than 5 numbers", severity: "error" }); +// handleOpen(); +// return; +// } + +// const newBidding: Bidding = { +// number: ind, +// }; + +// setBiddings([...biddings, newBidding]); +// }; + +// return ( +//
+//
+//
+//
+//

Bidding List

+// +//
+// {bidders != null && ( +//
+//
+//
Male Quota: {bidders.quota.Male}
+//
Female Quota: {bidders.quota.Female}
+//
+// {bidders.Male.length > 0 || bidders.Female.length > 0 ? ( +// +// +// +// +// +// +// +// + +// {Object.keys(bidders) +// .filter(category => category == "Male" || category == "Female") +// .map( +// (category, index: number) => +// bidders[category].length > 0 && ( +// <> +// +// +// +// +// +// {bidders[category].map((item: User, subIndex: number) => ( +// +// +// +// +// +// +// ))} +// +// ), +// )} +// +//
CategoryNamePointsSports
{category}
{item.username}{item.points}{getNames(item.teams)}
+// ) : ( +//

No Bids Found!!!

+// )} +//
+// )} +//
+//

Current Points : {points}

+// +//
+//
+//
+// ); +// }; + +// export default Modal; diff --git a/src/app/components/NavBar.tsx b/src/app/components/NavBar.tsx index a81a13e..5a6f2a2 100644 --- a/src/app/components/NavBar.tsx +++ b/src/app/components/NavBar.tsx @@ -2,6 +2,7 @@ import React from "react"; +import { Icon } from "@iconify/react"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { useDispatch } from "react-redux"; @@ -57,7 +58,7 @@ export default function NavBar() {
  • - + - CCA Booklet + Jersey Bidding
  • - + - CCA Signup + CCA Booklet
  • diff --git a/src/app/dashboard/ccas/page.tsx b/src/app/dashboard/ccas/page.tsx index 207bf55..359d94b 100644 --- a/src/app/dashboard/ccas/page.tsx +++ b/src/app/dashboard/ccas/page.tsx @@ -217,6 +217,7 @@ const CCAComponent: React.FC = () => { toast({ title: "Added Successfully", }); + setFlag(true) } setReason(""); setIsModalOpen(false); diff --git a/src/app/dashboard/jersey/page.tsx b/src/app/dashboard/jersey/page.tsx index 52f2974..3918c2a 100644 --- a/src/app/dashboard/jersey/page.tsx +++ b/src/app/dashboard/jersey/page.tsx @@ -1,62 +1,111 @@ "use client"; // This is a client component 👈🏽 -import React from "react"; +import React, { useEffect, useState } from "react"; import type { AlertColor } from "@mui/material"; +import { useQuery } from "@tanstack/react-query"; import axios from "axios"; +import { useRouter } from "next/navigation"; +import { useSelector } from "react-redux"; -export interface Bidding { - number: number; -} +import { getUserBiddings, getUserEligibleBids } from "../../api/jerseyBidding"; +import BiddingTable from "../../components/BiddingTable"; +import Loading from "../../components/Loading"; +import { selectUser } from "../../redux/Resources/userSlice"; -export interface Teams { - name: String; - shareable: boolean; -} +import { BiddingData, EligibleBids, UserBid } from "./types"; export interface ToastMessage { message: String; severity: AlertColor; // Possible to create enum in the future } +// new types + // Create an instance of axios with credentials axios.defaults.withCredentials = true; // @TODO This component is temporarily disabled, please fix linting issues const Jersey: React.FC = () => { - // const user = useSelector(selectUser); - // const router = useRouter(); + const user = useSelector(selectUser); + const router = useRouter(); - // const [isClient, setIsClient] = useState(false); + const [isClient, setIsClient] = useState(false); - // const userBiddings: Bidding[] = []; + // Does a call for all bids + const getBids = async () => { + try { + const response = await axios.get(`${process.env.NEXT_PUBLIC_BACKEND_URL}/jersey/list`); - // // State to manage error toast throughout app + console.log("response", response.data.data); - // // State for the Snackbar component - // // Does a call for eligible bids. API still WIP - // const getEligibleBids = async () => { + if (response.data.success) { + // console.log("This is eligible bids" + JSON.stringify(response.data.data)); + return response.data.data; + } + } catch (error) { + console.error("Error during getting allowed bids", error); + } + }; + + // Does a call for User's bidding info + // const getUserBiddings = async () => { // try { - // const response = await axios.get(`${process.env.NEXT_PUBLIC_BACKEND_URL}/jersey/eligible`); + // const response = await axios.get(`${process.env.NEXT_PUBLIC_BACKEND_URL}/jersey/info`); + + // console.log("response", response.data.data); // if (response.data.success) { - // setAllowedBids(response.data.data.jerseys); - // console.log("This is eligible bids" + JSON.stringify(response.data.data.jerseys)); + // console.log("This is eligible bids" + JSON.stringify(response.data.data)); + // return response.data.data; // } // } catch (error) { - // console.error("Error during getting allowed bids", error); + // console.error("Error during getting user bids", error); // } // }; - // // Does a call for user's teams + const { + data: bids, + status: bidsStatus, + refetch: refetchBids, + } = useQuery({ + queryKey: ["bids"], + queryFn: getBids, + }); + + const { + data: userBids, + status: userBidsStatus, + refetch: refetchUserBids, + } = useQuery({ + queryKey: ["user_bids"], + queryFn: getUserBiddings, + }); + + const { + data: userEligibleBids, + status: userEligibleBidsStatus, + refetch: refetchUserElligbleBids, + } = useQuery({ + queryKey: ["user_eligible_bids"], + queryFn: getUserEligibleBids, + }); + + // console.log('bids',bids); + // console.log("userEligible", userEligibleBids); + // State to manage error toast throughout app + + // State for the Snackbar component + + // Does a call for user's teams // const getUserTeam = async () => { // try { // const response = await axios.get(`${process.env.NEXT_PUBLIC_BACKEND_URL}/team/info`); // if (response.data.success) { - // setTeams(response.data.data.teams); + // // setTeams(response.data.data.teams); // console.log("This is user's team" + JSON.stringify(response.data.data.teams)); // } // } catch (error) { @@ -64,32 +113,41 @@ const Jersey: React.FC = () => { // } // }; - // // Saves changes to user_biddings in local storage + // Saves changes to user_biddings in local storage // useEffect(() => { // localStorage.setItem("user_biddings", JSON.stringify(biddings)); // }, [biddings]); - // useEffect(() => { - // setIsClient(true); // Indicate that client has been rendered - // getEligibleBids(); // Get all eligible bids when page renders - // getUserTeam(); // Get user's team when page renders - // }, []); - - // // If not authorized, then redirects the user - // useEffect(() => { - // if (user == null) { - // router.push("/"); - // } - // }, [user, router]); - - // return !isClient || user == null ? ( - // - // ) : ( - //
    {/* Your content goes here */}
    - // ); + useEffect(() => { + setIsClient(true); // Indicate that client has been rendered + // getEligibleBids(); // Get all eligible bids when page renders + // getUserTeam(); // Get user's team when page renders + }, []); + + // If not authorized, then redirects the user + useEffect(() => { + if (user == null) { + router.push("/"); + } + }, [user, router]); + + return !isClient || user == null ? ( + + ) : ( +
    + {userBids === undefined ? ( + + ) : ( + + )} +
    + ); return ; }; -const Loading = () =>
    Loading...
    ; - export default Jersey; diff --git a/src/app/dashboard/jersey/types.ts b/src/app/dashboard/jersey/types.ts new file mode 100644 index 0000000..7009bcb --- /dev/null +++ b/src/app/dashboard/jersey/types.ts @@ -0,0 +1,58 @@ +export interface UserInfo { + round: number; + points: number; + isAllocated: boolean; + jersey?: JerseyType; // Only present if isAllocated is true + teams: TeamContainer[]; +} + +export interface JerseyType { + number: number; + quota: Quota; +} + +export interface EligibleBids { + jerseys: number[]; +} + +export interface Quota { + male: number; + female: number; +} + +interface Team { + name: string; + shareable: boolean; +} + +interface TeamContainer { + team: Team; +} + +interface Bid { + jersey: JerseyType; + priority: number; +} + +interface System { + bidOpen: string; // Assuming it's a string (ISO date) + bidClose: string; // Assuming it's a string (ISO date) + bidRound: number; +} + +export interface UserBid { + info: UserInfo; + bids: Bid[]; + system: System; + canBid: boolean; +} + +export interface Bidding { + male: any[]; + female: any[]; + quota: Quota; +} + +export interface BiddingData { + [jerseyNumber: number]: Bidding; +} diff --git a/src/app/favicon.ico b/src/app/favicon.ico index 718d6fe..e864393 100644 Binary files a/src/app/favicon.ico and b/src/app/favicon.ico differ diff --git a/src/app/redux/provider.tsx b/src/app/redux/provider.tsx index 456aeb4..4af455d 100644 --- a/src/app/redux/provider.tsx +++ b/src/app/redux/provider.tsx @@ -2,10 +2,17 @@ import React from "react"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { Provider } from "react-redux"; import { store } from "@/src/app/redux/store"; export function Providers({ children }: { children: React.ReactNode }) { - return {children}; + const queryClient = new QueryClient(); + + return ( + + {children}; + + ); }