generated from codersforcauses/django-nextjs-template
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' into issue-20-Create_Team_Models
- Loading branch information
Showing
46 changed files
with
5,152 additions
and
678 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
}; |
Oops, something went wrong.