diff --git a/components.json b/components.json new file mode 100644 index 00000000..8b9622e5 --- /dev/null +++ b/components.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "tailwind.config.ts", + "css": "src/styles/globals.css", + "baseColor": "neutral", + "cssVariables": false, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils" + } +} \ No newline at end of file diff --git a/package.json b/package.json index 29e2a35a..728946d4 100644 --- a/package.json +++ b/package.json @@ -14,12 +14,13 @@ "@dnd-kit/core": "^6.1.0", "@dnd-kit/sortable": "^8.0.0", "@firebase/util": "^1.9.7", - "@headlessui/react": "^1.7.13", + "@headlessui/react": "^2.1.2", "@mdi/js": "^7.1.96", "@mdi/react": "^1.6.1", - "@next/font": "13.1.6", "@paypal/paypal-js": "^7.1.0", "@paypal/react-paypal-js": "^8.1.3", + "@radix-ui/react-icons": "^1.3.0", + "@radix-ui/react-popover": "^1.1.1", "@react-pdf/renderer": "^3.1.14", "@react-spring/web": "^9.7.4", "@tanstack/react-table": "^8.10.1", @@ -30,7 +31,8 @@ "axios": "^1.3.5", "bcrypt": "^5.1.1", "chart.js": "^4.2.1", - "clsx": "^1.2.1", + "class-variance-authority": "^0.7.0", + "clsx": "^2.1.1", "countries-list": "^3.0.1", "country-codes-list": "^1.6.11", "currency-symbol-map": "^5.1.0", @@ -48,7 +50,7 @@ "lodash": "^4.17.21", "moment": "^2.29.4", "moment-timezone": "^0.5.44", - "next": "13.1.6", + "next": "^14.2.5", "nodemailer": "^6.9.5", "nodemailer-express-handlebars": "^6.1.0", "primeicons": "^6.0.1", @@ -77,7 +79,9 @@ "short-unique-id": "5.0.2", "stripe": "^13.10.0", "swr": "^2.1.3", + "tailwind-merge": "^2.5.2", "tailwind-scrollbar-hide": "^1.1.7", + "tailwindcss-animate": "^1.0.7", "typescript": "4.9.5", "use-file-picker": "^2.1.0", "uuid": "^9.0.0", diff --git a/src/components/List.tsx b/src/components/List.tsx index 19781bf4..0e76b663 100644 --- a/src/components/List.tsx +++ b/src/components/List.tsx @@ -1,10 +1,11 @@ -import {Column, flexRender, getCoreRowModel, useReactTable} from "@tanstack/react-table"; +import {Column, flexRender, getCoreRowModel, getSortedRowModel, useReactTable} from "@tanstack/react-table"; export default function List({data, columns}: {data: T[]; columns: any[]}) { const table = useReactTable({ data, columns: columns, getCoreRowModel: getCoreRowModel(), + getSortedRowModel: getSortedRowModel(), }); return ( @@ -13,8 +14,22 @@ export default function List({data, columns}: {data: T[]; columns: any[]}) { {table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header) => ( - - {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())} + + {header.isPlaceholder ? null : ( + <> +
+ {flexRender(header.column.columnDef.header, header.getContext())} + {{ + asc: " 🔼", + desc: " 🔽", + }[header.column.getIsSorted() as string] ?? null} +
+ + )} ))} diff --git a/src/components/ui/popover.tsx b/src/components/ui/popover.tsx new file mode 100644 index 00000000..7bc4061e --- /dev/null +++ b/src/components/ui/popover.tsx @@ -0,0 +1,31 @@ +import * as React from "react" +import * as PopoverPrimitive from "@radix-ui/react-popover" + +import { cn } from "@/lib/utils" + +const Popover = PopoverPrimitive.Root + +const PopoverTrigger = PopoverPrimitive.Trigger + +const PopoverAnchor = PopoverPrimitive.Anchor + +const PopoverContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( + + + +)) +PopoverContent.displayName = PopoverPrimitive.Content.displayName + +export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor } diff --git a/src/dashboards/AssignmentCard.tsx b/src/dashboards/AssignmentCard.tsx index 1886cedd..720708f7 100644 --- a/src/dashboards/AssignmentCard.tsx +++ b/src/dashboards/AssignmentCard.tsx @@ -11,8 +11,10 @@ import {useAssignmentArchive} from "@/hooks/useAssignmentArchive"; import {uniqBy} from "lodash"; import {useAssignmentUnarchive} from "@/hooks/useAssignmentUnarchive"; import {getUserName} from "@/utils/users"; +import {User} from "@/interfaces/user"; interface Props { + users: User[]; onClick?: () => void; allowDownload?: boolean; reload?: Function; @@ -35,9 +37,8 @@ export default function AssignmentCard({ reload, allowArchive, allowUnarchive, + users, }: Assignment & Props) { - const {users} = useUsers(); - const renderPdfIcon = usePDFDownload("assignments"); const renderArchiveIcon = useAssignmentArchive(id, reload); const renderUnarchiveIcon = useAssignmentUnarchive(id, reload); diff --git a/src/dashboards/Corporate.tsx b/src/dashboards/Corporate.tsx index d0d7099c..eb6a70d5 100644 --- a/src/dashboards/Corporate.tsx +++ b/src/dashboards/Corporate.tsx @@ -339,7 +339,7 @@ export default function CorporateDashboard({user}: Props) {

Active Assignments ({assignments.filter(activeFilter).length})

{assignments.filter(activeFilter).map((a) => ( - setSelectedAssignment(a)} key={a.id} /> + setSelectedAssignment(a)} key={a.id} /> ))}
@@ -355,6 +355,7 @@ export default function CorporateDashboard({user}: Props) { {assignments.filter(futureFilter).map((a) => ( { setSelectedAssignment(a); setIsCreatingAssignment(true); @@ -370,6 +371,7 @@ export default function CorporateDashboard({user}: Props) { {assignments.filter(pastFilter).map((a) => ( setSelectedAssignment(a)} key={a.id} allowDownload @@ -385,6 +387,7 @@ export default function CorporateDashboard({user}: Props) { {assignments.filter(archivedFilter).map((a) => ( setSelectedAssignment(a)} key={a.id} allowDownload diff --git a/src/dashboards/MasterCorporate.tsx b/src/dashboards/MasterCorporate.tsx index 0d8c4996..160f8dea 100644 --- a/src/dashboards/MasterCorporate.tsx +++ b/src/dashboards/MasterCorporate.tsx @@ -21,6 +21,7 @@ import { BsArrowRepeat, BsPlus, BsPersonFillGear, + BsFilter, } from "react-icons/bs"; import UserCard from "@/components/UserCard"; import useGroups from "@/hooks/useGroups"; @@ -44,16 +45,23 @@ import {createColumn, createColumnHelper} from "@tanstack/react-table"; import List from "@/components/List"; import {getUserCorporate} from "@/utils/groups"; import {getCorporateUser, getUserCompanyName} from "@/resources/user"; -import {Switch} from "@headlessui/react"; import Checkbox from "@/components/Low/Checkbox"; -import {uniq, uniqBy} from "lodash"; +import {groupBy, uniq, uniqBy} from "lodash"; import Select from "@/components/Low/Select"; +import {Menu, MenuButton, MenuItem, MenuItems} from "@headlessui/react"; +import {Popover, PopoverContent, PopoverTrigger} from "@/components/ui/popover"; interface Props { user: MasterCorporateUser; } -type StudentPerformanceItem = User & {corporate?: CorporateUser; group: string}; +const activeFilter = (a: Assignment) => + moment(a.endDate).isAfter(moment()) && moment(a.startDate).isBefore(moment()) && a.assignees.length > a.results.length; +const pastFilter = (a: Assignment) => (moment(a.endDate).isBefore(moment()) || a.assignees.length === a.results.length) && !a.archived; +const archivedFilter = (a: Assignment) => a.archived; +const futureFilter = (a: Assignment) => moment(a.startDate).isAfter(moment()); + +type StudentPerformanceItem = User & {corporate?: CorporateUser; group?: Group}; const StudentPerformanceList = ({items, stats, users, groups}: {items: StudentPerformanceItem[]; stats: Stat[]; users: User[]; groups: Group[]}) => { const [isShowingAmount, setIsShowingAmount] = useState(false); const [availableCorporates] = useState( @@ -62,8 +70,15 @@ const StudentPerformanceList = ({items, stats, users, groups}: {items: StudentPe "id", ), ); + const [availableGroups] = useState( + uniqBy( + items.map((x) => x.group), + "id", + ), + ); const [selectedCorporate, setSelectedCorporate] = useState(null); + const [selectedGroup, setSelectedGroup] = useState(null); const columnHelper = createColumnHelper(); @@ -82,7 +97,7 @@ const StudentPerformanceList = ({items, stats, users, groups}: {items: StudentPe }), columnHelper.accessor("group", { header: "Group", - cell: (info) => info.getValue(), + cell: (info) => info.getValue()?.name || "N/A", }), columnHelper.accessor("corporate", { header: "Corporate", @@ -184,40 +199,80 @@ const StudentPerformanceList = ({items, stats, users, groups}: {items: StudentPe const filterUsers = (data: StudentPerformanceItem[]) => { console.log(data, selectedCorporate); const filterByCorporate = (item: StudentPerformanceItem) => item.corporate?.id === selectedCorporate?.id; + const filterByGroup = (item: StudentPerformanceItem) => item.group?.id === selectedGroup?.id; const filters: ((item: StudentPerformanceItem) => boolean)[] = []; if (selectedCorporate !== null) filters.push(filterByCorporate); + if (selectedGroup !== null) filters.push(filterByGroup); return filters.reduce((d, f) => d.filter(f), data); }; return (
-
- ({ + value: x?.id || "N/A", + label: x?.corporateInformation?.companyInformation?.name || x?.name || "N/A", + }))} + isClearable + value={ + selectedCorporate === null + ? null + : { + value: selectedCorporate?.id || "N/A", + label: + selectedCorporate?.corporateInformation?.companyInformation?.name || + selectedCorporate?.name || + "N/A", + } + } + placeholder="Select a Corporate..." + onChange={(value) => + !value + ? setSelectedCorporate(null) + : setSelectedCorporate( + value.value === "N/A" ? undefined : availableCorporates.find((x) => x?.id === value.value), + ) + } + /> +