diff --git a/app/layout.tsx b/app/layout.tsx index d0aff87..6be4789 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,5 +1,6 @@ import "./globals.scss"; +import "react-datepicker/dist/react-datepicker.css"; import { Navigation } from "@/components/Navigation"; import Sidebar from "@/components/Sidebar"; import { Ubuntu } from "next/font/google"; diff --git a/app/page.tsx b/app/page.tsx index 235faa2..a3fcf27 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -89,7 +89,7 @@ export default function Home() {
{top.length > 0 ? ( -
+
{top[1] ? ( diff --git a/app/staffing/page.tsx b/app/staffing/page.tsx index 498d5b2..6ca6307 100644 --- a/app/staffing/page.tsx +++ b/app/staffing/page.tsx @@ -1,33 +1,48 @@ "use client"; import AuthRoute from "@/components/AuthRoute"; -import { FormField, FormItem, FormMessage } from "@/components/ui/form"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; import { Input } from "@/components/ui/input"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Response } from "@/models/response"; import { zodResolver } from "@hookform/resolvers/zod"; import { useRouter } from "next/navigation"; -import { useContext, useEffect, useState } from "react"; -import { Row, Col, FormLabel, FormControl, Button, Spinner } from "react-bootstrap"; -import { Form, useForm } from "react-hook-form"; +import { useEffect, useState } from "react"; +import { Row, Col, Button, Spinner } from "react-bootstrap"; +import DatePicker from "react-datepicker"; +import { useForm } from "react-hook-form"; import { toast } from "react-toastify"; import { z } from "zod"; -import { AuthContext } from "../Providers"; - const formSchema = z.object({ - cid: z.number().positive(), - fullName: z.string().nonempty(), - email: z.string().email(), organization: z.string().nonempty(), estimatedPilots: z.number().positive(), start: z.date(), duration: z.string().nonempty(), }); +const generateDurationOptions = () => { + const options = []; + for (let hours = 1; hours <= 12; hours++) { + for (const minutes of [0, 30]) { + const formattedHours = String(hours).padStart(2, "0"); + const formattedMinutes = String(minutes).padStart(2, "0"); + options.push(`${formattedHours}:${formattedMinutes}`); + } + } + return options; +}; + export default function StaffingRequest() { - const authContext = useContext(AuthContext); const router = useRouter(); const [loading, setLoading] = useState(true); @@ -35,9 +50,6 @@ export default function StaffingRequest() { const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { - cid: authContext?.user?.cid ?? 0, - fullName: authContext?.user?.fullName ?? "", - email: authContext?.user?.email ?? "", organization: "", estimatedPilots: 0, start: new Date(), @@ -47,6 +59,7 @@ export default function StaffingRequest() { useEffect(() => { document.title = "Staffing Request | Memphis ARTCC"; + setLoading(false); }, []); async function onSubmit(values: z.infer) { @@ -82,59 +95,12 @@ export default function StaffingRequest() {
- Staffing Request + Staffing Request
- - - ( - - CID - - - - - - )} - /> - - - ( - - Name - - - - - - )} - /> - - - ( - - Email - - - - - - )} - /> - - Organization - + @@ -157,9 +123,9 @@ export default function StaffingRequest() { name="estimatedPilots" render={({ field }) => ( - Organization + Estimated Pilots - + @@ -172,9 +138,15 @@ export default function StaffingRequest() { name="start" render={({ field }) => ( - Start Date & Time + Start - + form.setValue("start", date as Date)} + showTimeSelect + dateFormat="Pp" + className="w-full rounded-md border p-2 text-black" + /> @@ -187,9 +159,20 @@ export default function StaffingRequest() { name="duration" render={({ field }) => ( - Duration + Duration - + diff --git a/app/statistics/page.tsx b/app/statistics/page.tsx index bc61a01..54cdc8d 100644 --- a/app/statistics/page.tsx +++ b/app/statistics/page.tsx @@ -150,22 +150,22 @@ export default function StaffingRequest() { {getUserStatusString(statsEntry.status)} - {statsEntry.deliveryHours} + {statsEntry.deliveryHours.toFixed(2)} - {statsEntry.groundHours} + {statsEntry.groundHours.toFixed(2)} - {statsEntry.towerHours} + {statsEntry.towerHours.toFixed(2)} - {statsEntry.traconHours} + {statsEntry.traconHours.toFixed(2)} - {statsEntry.centerHours} + {statsEntry.centerHours.toFixed(2)} - {statsEntry.totalHours} + {statsEntry.totalHours.toFixed(2)} ))} diff --git a/components/ui/select.tsx b/components/ui/select.tsx new file mode 100644 index 0000000..e800830 --- /dev/null +++ b/components/ui/select.tsx @@ -0,0 +1,159 @@ +"use client"; + +import { cn } from "@/lib/utils"; +import * as SelectPrimitive from "@radix-ui/react-select"; +import { Check, ChevronDown, ChevronUp } from "lucide-react"; +import * as React from "react"; + +const Select = SelectPrimitive.Root; + +const SelectGroup = SelectPrimitive.Group; + +const SelectValue = SelectPrimitive.Value; + +const SelectTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + span]:line-clamp-1 dark:border-gray-800 dark:bg-gray-950 dark:ring-offset-gray-950 dark:data-[placeholder]:text-gray-400 dark:focus:ring-gray-300", + className + )} + {...props} + > + {children} + + + + +)); +SelectTrigger.displayName = SelectPrimitive.Trigger.displayName; + +const SelectScrollUpButton = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)); +SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName; + +const SelectScrollDownButton = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)); +SelectScrollDownButton.displayName = + SelectPrimitive.ScrollDownButton.displayName; + +const SelectContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, position = "popper", ...props }, ref) => ( + + + + + {children} + + + + +)); +SelectContent.displayName = SelectPrimitive.Content.displayName; + +const SelectLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +SelectLabel.displayName = SelectPrimitive.Label.displayName; + +const SelectItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + + {children} + +)); +SelectItem.displayName = SelectPrimitive.Item.displayName; + +const SelectSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +SelectSeparator.displayName = SelectPrimitive.Separator.displayName; + +export { + Select, + SelectGroup, + SelectValue, + SelectTrigger, + SelectContent, + SelectLabel, + SelectItem, + SelectSeparator, + SelectScrollUpButton, + SelectScrollDownButton, +}; diff --git a/package.json b/package.json index e3b563f..23b3ca0 100644 --- a/package.json +++ b/package.json @@ -14,18 +14,23 @@ "@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-dropdown-menu": "^2.1.2", "@radix-ui/react-label": "^2.1.0", + "@radix-ui/react-popover": "^1.1.6", "@radix-ui/react-progress": "^1.1.0", + "@radix-ui/react-select": "^2.1.6", "@radix-ui/react-separator": "^1.1.0", - "@radix-ui/react-slot": "^1.1.0", + "@radix-ui/react-slot": "^1.1.2", "@radix-ui/react-tooltip": "^1.1.4", "bootstrap": "^5.3.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "date-fns": "^4.1.0", "jwt-decode": "^4.0.0", "lucide-react": "^0.483.0", "next": "15.2.3", "react": "19.0.0", "react-bootstrap": "^2.10.6", + "react-datepicker": "^8.2.1", + "react-day-picker": "8.10.1", "react-dom": "19.0.0", "react-hook-form": "^7.53.2", "react-toastify": "^11.0.5", diff --git a/yarn.lock b/yarn.lock index 12f7552..ccd6b8c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -117,13 +117,22 @@ "@floating-ui/core" "^1.6.0" "@floating-ui/utils" "^0.2.9" -"@floating-ui/react-dom@^2.0.0": +"@floating-ui/react-dom@^2.0.0", "@floating-ui/react-dom@^2.1.2": version "2.1.2" resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.1.2.tgz#a1349bbf6a0e5cb5ded55d023766f20a4d439a31" integrity sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A== dependencies: "@floating-ui/dom" "^1.0.0" +"@floating-ui/react@^0.27.3": + version "0.27.5" + resolved "https://registry.yarnpkg.com/@floating-ui/react/-/react-0.27.5.tgz#27a6e63a8ef35eb8712ef304a154ea706da26814" + integrity sha512-BX3jKxo39Ba05pflcQmqPPwc0qdNsdNi/eweAFtoIdrJWNen2sVEWMEac3i6jU55Qfx+lOcdMNKYn2CtWmlnOQ== + dependencies: + "@floating-ui/react-dom" "^2.1.2" + "@floating-ui/utils" "^0.2.9" + tabbable "^6.0.0" + "@floating-ui/utils@^0.2.9": version "0.2.9" resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.9.tgz#50dea3616bc8191fb8e112283b49eaff03e78429" @@ -507,6 +516,11 @@ resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f" integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A== +"@radix-ui/number@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/number/-/number-1.1.0.tgz#1e95610461a09cdf8bb05c152e76ca1278d5da46" + integrity sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ== + "@radix-ui/primitive@1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-1.1.1.tgz#fc169732d755c7fbad33ba8d0cd7fd10c90dc8e3" @@ -640,6 +654,27 @@ aria-hidden "^1.2.4" react-remove-scroll "^2.6.3" +"@radix-ui/react-popover@^1.1.6": + version "1.1.6" + resolved "https://registry.yarnpkg.com/@radix-ui/react-popover/-/react-popover-1.1.6.tgz#699634dbc7899429f657bb590d71fb3ca0904087" + integrity sha512-NQouW0x4/GnkFJ/pRqsIS3rM/k97VzKnVb2jB7Gq7VEGPy5g7uNV1ykySFt7eWSp3i2uSGFwaJcvIRJBAHmmFg== + dependencies: + "@radix-ui/primitive" "1.1.1" + "@radix-ui/react-compose-refs" "1.1.1" + "@radix-ui/react-context" "1.1.1" + "@radix-ui/react-dismissable-layer" "1.1.5" + "@radix-ui/react-focus-guards" "1.1.1" + "@radix-ui/react-focus-scope" "1.1.2" + "@radix-ui/react-id" "1.1.0" + "@radix-ui/react-popper" "1.2.2" + "@radix-ui/react-portal" "1.1.4" + "@radix-ui/react-presence" "1.1.2" + "@radix-ui/react-primitive" "2.0.2" + "@radix-ui/react-slot" "1.1.2" + "@radix-ui/react-use-controllable-state" "1.1.0" + aria-hidden "^1.2.4" + react-remove-scroll "^2.6.3" + "@radix-ui/react-popper@1.2.2": version "1.2.2" resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-1.2.2.tgz#d2e1ee5a9b24419c5936a1b7f6f472b7b412b029" @@ -702,6 +737,33 @@ "@radix-ui/react-use-callback-ref" "1.1.0" "@radix-ui/react-use-controllable-state" "1.1.0" +"@radix-ui/react-select@^2.1.6": + version "2.1.6" + resolved "https://registry.yarnpkg.com/@radix-ui/react-select/-/react-select-2.1.6.tgz#79c07cac4de0188e6f7afb2720a87a0405d88849" + integrity sha512-T6ajELxRvTuAMWH0YmRJ1qez+x4/7Nq7QIx7zJ0VK3qaEWdnWpNbEDnmWldG1zBDwqrLy5aLMUWcoGirVj5kMg== + dependencies: + "@radix-ui/number" "1.1.0" + "@radix-ui/primitive" "1.1.1" + "@radix-ui/react-collection" "1.1.2" + "@radix-ui/react-compose-refs" "1.1.1" + "@radix-ui/react-context" "1.1.1" + "@radix-ui/react-direction" "1.1.0" + "@radix-ui/react-dismissable-layer" "1.1.5" + "@radix-ui/react-focus-guards" "1.1.1" + "@radix-ui/react-focus-scope" "1.1.2" + "@radix-ui/react-id" "1.1.0" + "@radix-ui/react-popper" "1.2.2" + "@radix-ui/react-portal" "1.1.4" + "@radix-ui/react-primitive" "2.0.2" + "@radix-ui/react-slot" "1.1.2" + "@radix-ui/react-use-callback-ref" "1.1.0" + "@radix-ui/react-use-controllable-state" "1.1.0" + "@radix-ui/react-use-layout-effect" "1.1.0" + "@radix-ui/react-use-previous" "1.1.0" + "@radix-ui/react-visually-hidden" "1.1.2" + aria-hidden "^1.2.4" + react-remove-scroll "^2.6.3" + "@radix-ui/react-separator@^1.1.0": version "1.1.2" resolved "https://registry.yarnpkg.com/@radix-ui/react-separator/-/react-separator-1.1.2.tgz#24c5450db20f341f2b743ed4b07b907e18579216" @@ -709,7 +771,7 @@ dependencies: "@radix-ui/react-primitive" "2.0.2" -"@radix-ui/react-slot@1.1.2", "@radix-ui/react-slot@^1.1.0": +"@radix-ui/react-slot@1.1.2", "@radix-ui/react-slot@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.1.2.tgz#daffff7b2bfe99ade63b5168407680b93c00e1c6" integrity sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ== @@ -758,6 +820,11 @@ resolved "https://registry.yarnpkg.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz#3c2c8ce04827b26a39e442ff4888d9212268bd27" integrity sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w== +"@radix-ui/react-use-previous@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-previous/-/react-use-previous-1.1.0.tgz#d4dd37b05520f1d996a384eb469320c2ada8377c" + integrity sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og== + "@radix-ui/react-use-rect@1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@radix-ui/react-use-rect/-/react-use-rect-1.1.0.tgz#13b25b913bd3e3987cc9b073a1a164bb1cf47b88" @@ -1460,6 +1527,11 @@ data-view-byte-offset@^1.0.1: es-errors "^1.3.0" is-data-view "^1.0.1" +date-fns@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-4.1.0.tgz#64b3d83fff5aa80438f5b1a633c2e83b8a1c2d14" + integrity sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg== + debug@^3.2.7: version "3.2.7" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" @@ -2964,6 +3036,20 @@ react-bootstrap@^2.10.6: uncontrollable "^7.2.1" warning "^4.0.3" +react-datepicker@^8.2.1: + version "8.2.1" + resolved "https://registry.yarnpkg.com/react-datepicker/-/react-datepicker-8.2.1.tgz#f710453f62689db9f7fdbbbcc30ec95ab28750f8" + integrity sha512-1pyALWM9mTZ7DG7tfcApwBy2kkld9Kz/EI++LhPnoXJAASbvuq6fdsDfkoB3q1JrxF7vhghVmQ759H/rOwUNNw== + dependencies: + "@floating-ui/react" "^0.27.3" + clsx "^2.1.1" + date-fns "^4.1.0" + +react-day-picker@8.10.1: + version "8.10.1" + resolved "https://registry.yarnpkg.com/react-day-picker/-/react-day-picker-8.10.1.tgz#4762ec298865919b93ec09ba69621580835b8e80" + integrity sha512-TMx7fNbhLk15eqcMt+7Z7S2KF7mfTId/XJDjKE8f+IUcFn0l08/kI4FiYTL/0yuOLmEcbR4Fwe3GJf/NiiMnPA== + react-dom@19.0.0: version "19.0.0" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.0.0.tgz#43446f1f01c65a4cd7f7588083e686a6726cfb57" @@ -3493,6 +3579,11 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== +tabbable@^6.0.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-6.2.0.tgz#732fb62bc0175cfcec257330be187dcfba1f3b97" + integrity sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew== + tailwind-merge@^2.5.5: version "2.6.0" resolved "https://registry.yarnpkg.com/tailwind-merge/-/tailwind-merge-2.6.0.tgz#ac5fb7e227910c038d458f396b7400d93a3142d5"