Skip to content

Commit

Permalink
Merge branch 'main' into issue-20-Create_Team_Models
Browse files Browse the repository at this point in the history
  • Loading branch information
ErikaKK committed Jan 11, 2025
2 parents dc996f9 + e039d3e commit f2a06d9
Show file tree
Hide file tree
Showing 46 changed files with 5,152 additions and 678 deletions.
1 change: 1 addition & 0 deletions client/.husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#!/bin/sh

# shellcheck disable=SC1091
. "$(dirname -- "$0")/_/husky.sh"

cd ./client && npx lint-staged
2,775 changes: 2,149 additions & 626 deletions client/package-lock.json

Large diffs are not rendered by default.

21 changes: 18 additions & 3 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,39 @@
"format": "prettier -w src",
"format:check": "prettier -c src",
"typecheck": "tsc --noEmit",
"prepare": "cd .. && husky client/.husky"
"prepare": "cd .. && husky client/.husky",
"check-all": "npm run lint && npm run format:check && npm run typecheck"
},
"dependencies": {
"@hookform/resolvers": "^3.9.1",
"@radix-ui/react-dialog": "^1.1.4",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-select": "^2.1.2",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-slot": "^1.1.1",
"@tanstack/react-query": "^5.51.23",
"@tanstack/react-query-devtools": "^5.51.23",
"@types/js-cookie": "^3.0.6",
"autoprefixer": "^10.4.20",
"axios": "^1.7.3",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"is-inside-container": "^1.0.0",
"js-cookie": "^3.0.5",
"jwt-decode": "^4.0.0",
"lucide-react": "^0.427.0",
"mathjax": "^3.2.2",
"next": "^14.2.5",
"next-themes": "^0.4.4",
"node-tikzjax": "^1.0.3",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-hook-form": "^7.54.2",
"react-latex-next": "^3.0.0",
"tailwind-merge": "^2.4.0",
"tailwindcss-animate": "^1.0.7"
"tailwindcss-animate": "^1.0.7",
"zod": "^3.24.1",
"sonner": "^1.7.1",
"zustand": "^5.0.2"
},
"devDependencies": {
"@csstools/postcss-oklab-function": "^3.0.19",
Expand Down
176 changes: 176 additions & 0 deletions client/src/components/ui/Question/data-grid.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
import React, { useEffect, useState } from "react";

import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";

import { Button } from "../button";
import { Pagination } from "../pagination";

/**
* The Datagrid component is a flexible, paginated data table with sorting and navigation features.
*
* @param {DatagridProps} props - Props including datacontext (data array), onDataChange (callback for data update), and ChangePage (external control for current page).
*/
export function Datagrid({
datacontext,
onDataChange,
changePage,
}: DatagridProps) {
// State to track sorting direction
const [isAscending, setIsAscending] = useState(true);
// State for the current page number
const [currentPage, setCurrentPage] = useState(1);
// State to hold padded data for consistent rows
const [paddedData, setPaddedData] = useState<Question[]>([]);
// Number of items displayed per page
const itemsPerPage = 5;
// Calculate total pages based on the data length
const totalPages = Math.ceil(datacontext.length / itemsPerPage);

/**
* Handles sorting of the data based on a specified column.
* @param {keyof Question} column - Column key to sort by.
*/
const sortByColumn = (column: keyof Question) => {
const sortedData = [...datacontext].sort((a, b) => {
return isAscending
? a[column].localeCompare(b[column])
: b[column].localeCompare(a[column]);
});
setCurrentPage(1); // Reset to the first page after sorting
onDataChange(sortedData); // Update the parent with sorted data
setIsAscending(!isAscending); // Toggle sorting direction
};

/**
* Handles page change logic.
* @param {number} page - The new page number.
*/
const handlePageChange = (page: number) => {
if (page >= 1 && page <= totalPages) {
setCurrentPage(page);
}
};

/**
* Updates the displayed data based on the current page.
* Pads the data to ensure consistent rows (e.g., always 5 rows).
*/
useEffect(() => {
const indexOfLastItem = currentPage * itemsPerPage;
const indexOfFirstItem = indexOfLastItem - itemsPerPage;
const currentData = datacontext.slice(indexOfFirstItem, indexOfLastItem);

// Ensure paddedData always has 5 rows
const updatedPaddedData = [...currentData];
while (updatedPaddedData.length < itemsPerPage) {
updatedPaddedData.push({ name: "", category: "", difficulty: "" });
}

setPaddedData(updatedPaddedData);
}, [datacontext, currentPage]);

/**
* Make the default page always 1 when search button makes any change
*/
useEffect(() => {
setCurrentPage(changePage);
}, [datacontext]);

return (
<div>
<Table className="w-full border-collapse text-left shadow-md">
<TableHeader className="bg-black text-lg font-semibold">
<TableRow className="hover:bg-muted/0">
<TableHead className="w-1/4 rounded-tl-lg text-white">
<span>Name</span>
</TableHead>
<TableHead className="w-1/4">
<div className="flex items-center text-white">
<span>Category</span>
<span
className="ml-2 cursor-pointer"
onClick={() => sortByColumn("category")}
>
<svg
width="10"
height="19"
viewBox="0 0 10 19"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M5 19L0.669872 11.5H9.33013L5 19Z" fill="white" />
<path d="M5 0L9.33013 7.5H0.669873L5 0Z" fill="white" />
</svg>
</span>
</div>
</TableHead>
<TableHead className="w-1/4">
<div className="flex items-center text-white">
<span>Difficulty</span>
<span
className="ml-2 cursor-pointer"
onClick={() => sortByColumn("difficulty")}
>
<svg
width="10"
height="19"
viewBox="0 0 10 19"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M5 19L0.669872 11.5H9.33013L5 19Z" fill="white" />
<path d="M5 0L9.33013 7.5H0.669873L5 0Z" fill="white" />
</svg>
</span>
</div>
</TableHead>
<TableHead className="w-1/4 rounded-tr-lg text-white"></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{paddedData.map((item, index) => (
<TableRow
key={index}
className={"divide-gray-200 border-gray-50 text-sm text-black"}
>
<TableCell className="w-1/4">{item.name || "\u00A0"}</TableCell>
<TableCell className="w-1/4">
{item.category || "\u00A0"}
</TableCell>
<TableCell className="w-1/4">
{item.difficulty || "\u00A0"}
</TableCell>
<TableCell className="flex justify-evenly py-4">
{item.name ? (
<>
<Button>View</Button>
<Button variant={"destructive"}>Delete</Button>
</>
) : (
<div className="invisible flex">
<Button>View</Button>
<Button variant={"destructive"}>Delete</Button>
</div>
)}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>

<Pagination
totalPages={totalPages}
currentPage={currentPage}
onPageChange={(page) => handlePageChange(page)}
className="mr-20 mt-5 flex justify-end"
/>
</div>
);
}
170 changes: 170 additions & 0 deletions client/src/components/ui/breadcrumb.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import { Slot } from "@radix-ui/react-slot";
import { ChevronRight, MoreHorizontal } from "lucide-react";
import * as React from "react";

import { cn } from "@/lib/utils";
/**
* The `Breadcrumb` component provides navigation for users to understand their location within a hierarchy.
* @see {@link https://ui.shadcn.com/docs/components/breadcrumb} for more details.
* @example
* <Breadcrumb>
* <BreadcrumbList>
* <BreadcrumbItem>
* <BreadcrumbLink href="/home">Home</BreadcrumbLink>
* </BreadcrumbItem>
* <BreadcrumbSeparator />
* <BreadcrumbItem>
* <BreadcrumbLink href="/about">About</BreadcrumbLink>
* </BreadcrumbItem>
* </BreadcrumbList>
* </Breadcrumb>
*/
const Breadcrumb = React.forwardRef<
HTMLElement,
React.ComponentPropsWithoutRef<"nav"> & {
separator?: React.ReactNode;
}
>(({ ...props }, ref) => <nav ref={ref} aria-label="breadcrumb" {...props} />);
Breadcrumb.displayName = "Breadcrumb";

/**
* The `BreadcrumbList` component renders an ordered list (`<ol>`) for the breadcrumb structure.
*
* @example
* <BreadcrumbList>
* <BreadcrumbItem>...</BreadcrumbItem>
* </BreadcrumbList>
*/
const BreadcrumbList = React.forwardRef<
HTMLOListElement,
React.ComponentPropsWithoutRef<"ol">
>(({ className, ...props }, ref) => (
<ol
ref={ref}
className={cn(
"flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5",
className,
)}
{...props}
/>
));
BreadcrumbList.displayName = "BreadcrumbList";

/**
* The `BreadcrumbItem` component represents an individual breadcrumb in the navigation hierarchy.
*
* @example
* <BreadcrumbItem>
* <BreadcrumbLink href="/home">Home</BreadcrumbLink>
* </BreadcrumbItem>
*/
const BreadcrumbItem = React.forwardRef<
HTMLLIElement,
React.ComponentPropsWithoutRef<"li">
>(({ className, ...props }, ref) => (
<li
ref={ref}
className={cn("inline-flex items-center gap-1.5", className)}
{...props}
/>
));
BreadcrumbItem.displayName = "BreadcrumbItem";

/**
* The `BreadcrumbLink` component renders a clickable link within a breadcrumb item.
*
* @example
* <BreadcrumbLink href="/home">Home</BreadcrumbLink>
*/
const BreadcrumbLink = React.forwardRef<
HTMLAnchorElement,
React.ComponentPropsWithoutRef<"a"> & {
asChild?: boolean;
}
>(({ asChild, className, ...props }, ref) => {
const Comp = asChild ? Slot : "a";

return (
<Comp
ref={ref}
className={cn("transition-colors hover:text-foreground", className)}
{...props}
/>
);
});
BreadcrumbLink.displayName = "BreadcrumbLink";

/**
* The `BreadcrumbPage` component displays the current page in the breadcrumb hierarchy.
*
* @example
* <BreadcrumbPage>Current Page</BreadcrumbPage>
*/
const BreadcrumbPage = React.forwardRef<
HTMLSpanElement,
React.ComponentPropsWithoutRef<"span">
>(({ className, ...props }, ref) => (
<span
ref={ref}
role="link"
aria-disabled="true"
aria-current="page"
className={cn("font-normal text-foreground", className)}
{...props}
/>
));
BreadcrumbPage.displayName = "BreadcrumbPage";

/**
* The `BreadcrumbSeparator` component renders a separator (default is a right arrow) between breadcrumb items.
*
* @example
* <BreadcrumbSeparator />
*/
const BreadcrumbSeparator = ({
children,
className,
...props
}: React.ComponentProps<"li">) => (
<li
role="presentation"
aria-hidden="true"
className={cn("[&>svg]:h-3.5 [&>svg]:w-3.5", className)}
{...props}
>
{children ?? <ChevronRight />}
</li>
);
BreadcrumbSeparator.displayName = "BreadcrumbSeparator";

/**
* The `BreadcrumbEllipsis` component renders an ellipsis (`...`) to indicate truncated breadcrumbs.
*
* @example
* <BreadcrumbEllipsis />
*/
const BreadcrumbEllipsis = ({
className,
...props
}: React.ComponentProps<"span">) => (
<span
role="presentation"
aria-hidden="true"
className={cn("flex h-9 w-9 items-center justify-center", className)}
{...props}
>
<MoreHorizontal className="h-4 w-4" />
<span className="sr-only">More</span>
</span>
);
BreadcrumbEllipsis.displayName = "BreadcrumbEllipsis";

export {
Breadcrumb,
BreadcrumbEllipsis,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
};
Loading

0 comments on commit f2a06d9

Please sign in to comment.