Skip to content

Commit

Permalink
feat: filter and help feature added
Browse files Browse the repository at this point in the history
  • Loading branch information
bencodes07 committed May 12, 2024
1 parent b742bfc commit ff255a5
Show file tree
Hide file tree
Showing 5 changed files with 228 additions and 36 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"@commitlint/config-conventional": "^18.6.0",
"@hookform/resolvers": "^3.3.4",
"@next/eslint-plugin-next": "^14.1.0",
"@radix-ui/react-alert-dialog": "^1.0.5",
"@radix-ui/react-avatar": "^1.0.4",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-dropdown-menu": "^2.0.6",
Expand Down
75 changes: 66 additions & 9 deletions src/app/dashboard/bookings/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,45 @@ import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import { extractNameInitials } from "@/lib/utils";
import { useRouter } from "next/navigation";
import Scheduler from "@/components/dashboard/scheduler/Scheduler";
import { FaPlus } from "react-icons/fa6";
import { FiFilter } from "react-icons/fi";
import { FaPlus, FaRegCircleQuestion } from "react-icons/fa6";
import {
AlertDialog,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger
} from "@/components/ui/alert-dialog";

function Bookings() {
const [user, setUser] = useState<User | null>();
const [loading, setLoading] = useState(true);
const [searchQuery, setSearchQuery] = useState("");
const [events, setEvents] = useState<
Array<{ Id: number; Subject: string; Location: string; StartTime: Date; EndTime: Date; RecurrenceRule: string }>
>([
{
Id: 1,
Subject: "Scrum Meeting",
Location: "Office",
StartTime: new Date(2024, 4, 6, 14, 30),
EndTime: new Date(2024, 4, 6, 15, 30),
RecurrenceRule: ""
},
{
Id: 2,
Subject: "Scrum Meeting",
Location: "Office",
StartTime: new Date(2024, 4, 8, 17, 30),
EndTime: new Date(2024, 4, 8, 18, 30),
RecurrenceRule: ""
}
]);
const [filteredEvents, setFilteredEvents] = useState<
Array<{ Id: number; Subject: string; Location: string; StartTime: Date; EndTime: Date; RecurrenceRule: string }>
>([]);

const router = useRouter();

Expand All @@ -38,10 +71,15 @@ function Bookings() {
console.error("Failed to fetch user", error);
setLoading(false);
}
if (false) setEvents([]); // This is a dummy condition to avoid unused variable warning (events)
};
fetchUser().catch(console.error);
}, []);

useEffect(() => {
setFilteredEvents(events.filter((event) => event.Subject.toLowerCase().includes(searchQuery.toLowerCase())));
}, [searchQuery, events]);

const logout = async () => {
try {
await deleteToken();
Expand All @@ -65,9 +103,13 @@ function Bookings() {
<header className="flex w-full flex-row items-center justify-between">
<h1 className="m-4 font-medium text-foreground md:text-2xl">Bookings</h1>
<div className="flex items-center gap-x-6">
<Input className="w-[320px]" placeholder="Search"></Input>
<Input
className="w-[320px]"
placeholder="Search"
value={searchQuery}
onChange={(e) => { setSearchQuery(e.target.value); }}></Input>
<DropdownMenu modal={false}>
<DropdownMenuTrigger asChild className={"mr-4"}>
<DropdownMenuTrigger className={"mr-4"}>
<Button variant="ghost" className="relative size-8 rounded-full">
<Avatar className="size-10">
<AvatarFallback className={"bg-primary"}>{extractNameInitials(user?.name)}</AvatarFallback>
Expand Down Expand Up @@ -99,18 +141,33 @@ function Bookings() {
<div className={"mt-2 flex w-full items-center justify-between pl-4 text-foreground"}>
Your Appointments at a glance. Book a new appointment now!
<div className={"flex w-fit items-center justify-center gap-x-4 text-foreground"}>
<Button variant={"secondary"}>
<FiFilter className={"mr-1 font-bold"} />
Filter
</Button>
<AlertDialog>
<AlertDialogTrigger>
<Button variant={"secondary"}>
<FaRegCircleQuestion className={"mr-1 font-bold"} />
Help
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>You need help?</AlertDialogTitle>
<AlertDialogDescription className={"flex gap-x-2"}>
Contact <pre>&quot;boeckmannben{"<at>"}gmail.com&quot;</pre> for help
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter className={"text-foreground"}>
<AlertDialogCancel>Close</AlertDialogCancel>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
<Button className={"text-foreground"}>
<FaPlus className={"mr-1"} />
Book Appointment
</Button>
</div>
</div>
<div className="mt-4 flex h-fit w-full rounded-[20px] bg-subtle p-6">
<Scheduler />
<Scheduler openingHours={{ open: "13:00", close: "19:00" }} data={filteredEvents} />
</div>
</div>
);
Expand Down
39 changes: 12 additions & 27 deletions src/components/dashboard/scheduler/Scheduler.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,11 @@ import { registerLicense } from "@syncfusion/ej2-base";
import { Button } from "@/components/ui/button";
import { FaArrowRight, FaArrowLeft } from "react-icons/fa6";

function Scheduler() {
const data = [
{
Id: 1,
Subject: "Scrum Meeting",
Location: "Office",
StartTime: new Date(2024, 4, 6, 14, 30),
EndTime: new Date(2024, 4, 6, 15, 30),
RecurrenceRule: ""
},
{
Id: 2,
Subject: "Scrum Meeting",
Location: "Office",
StartTime: new Date(2024, 4, 8, 17, 30),
EndTime: new Date(2024, 4, 8, 18, 30),
RecurrenceRule: ""
}
];
const eventSettings = { dataSource: data };
function Scheduler(props: {
openingHours: { open: string; close: string };
data: Array<{ Id: number; Subject: string; Location: string; StartTime: Date; EndTime: Date; RecurrenceRule: string }>;
}) {
const eventSettings = { dataSource: props.data };

const [currentDate, setCurrentDate] = useState(new Date());

Expand Down Expand Up @@ -77,26 +62,26 @@ function Scheduler() {
<ViewsDirective>
<ViewDirective
option="WorkWeek"
startHour="13:00"
endHour="19:00"
startHour={props.openingHours.open}
endHour={props.openingHours.close}
timeScale={{ interval: 60, slotCount: 2 }}
eventTemplate={(props: { Subject: string; StartTime: Date; EndTime: Date }) => (
eventTemplate={(eventProps: { Subject: string; StartTime: Date; EndTime: Date }) => (
<div
className={"absolute z-10 ml-[-4px] size-full rounded-[12px] border-[2px] border-solid p-2"}
className={"absolute z-10 ml-[-4px] size-full rounded-[12px] border-2 border-solid p-2"}
style={{
borderColor: getRandomColor()
}}>
<h2 className={"font-bold"}>{props.Subject}</h2>
<h2 className={"font-bold"}>{eventProps.Subject}</h2>
<div className={"mt-1 flex items-center gap-x-2 text-xs text-muted-foreground"}>
{new Intl.DateTimeFormat("en-US", {
hour: "numeric",
minute: "numeric"
}).format(props.StartTime)}{" "}
}).format(eventProps.StartTime)}{" "}
<FaArrowRight />{" "}
{new Intl.DateTimeFormat("en-US", {
hour: "numeric",
minute: "numeric"
}).format(props.EndTime)}
}).format(eventProps.EndTime)}
</div>
</div>
)}
Expand Down
106 changes: 106 additions & 0 deletions src/components/ui/alert-dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
"use client";

import * as React from "react";
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";

import { cn } from "@/lib/utils";
import { buttonVariants } from "@/components/ui/button";

const AlertDialog = AlertDialogPrimitive.Root;

const AlertDialogTrigger = AlertDialogPrimitive.Trigger;

const AlertDialogPortal = AlertDialogPrimitive.Portal;

const AlertDialogOverlay = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Overlay
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
ref={ref}
/>
));
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName;

const AlertDialogContent = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
>(({ className, ...props }, ref) => (
<AlertDialogPortal>
<AlertDialogOverlay />
<AlertDialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
className
)}
{...props}
/>
</AlertDialogPortal>
));
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName;

const AlertDialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div className={cn("flex flex-col space-y-2 text-center sm:text-left", className)} {...props} />
);
AlertDialogHeader.displayName = "AlertDialogHeader";

const AlertDialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div className={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)} {...props} />
);
AlertDialogFooter.displayName = "AlertDialogFooter";

const AlertDialogTitle = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Title ref={ref} className={cn("text-lg font-semibold", className)} {...props} />
));
AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName;

const AlertDialogDescription = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Description ref={ref} className={cn("text-sm text-muted-foreground", className)} {...props} />
));
AlertDialogDescription.displayName = AlertDialogPrimitive.Description.displayName;

const AlertDialogAction = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Action>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Action ref={ref} className={cn(buttonVariants(), className)} {...props} />
));
AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName;

const AlertDialogCancel = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Cancel
ref={ref}
className={cn(buttonVariants({ variant: "outline" }), "mt-2 sm:mt-0", className)}
{...props}
/>
));
AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName;

export {
AlertDialog,
AlertDialogPortal,
AlertDialogOverlay,
AlertDialogTrigger,
AlertDialogContent,
AlertDialogHeader,
AlertDialogFooter,
AlertDialogTitle,
AlertDialogDescription,
AlertDialogAction,
AlertDialogCancel
};
43 changes: 43 additions & 0 deletions src/components/ui/alert.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import * as React from "react";
import { cva, type VariantProps } from "class-variance-authority";

import { cn } from "@/lib/utils";

const alertVariants = cva(
"relative w-full rounded-lg border p-4 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7",
{
variants: {
variant: {
default: "bg-background text-foreground",
destructive: "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive"
}
},
defaultVariants: {
variant: "default"
}
}
);

const Alert = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
>(({ className, variant, ...props }, ref) => (
<div ref={ref} role="alert" className={cn(alertVariants({ variant }), className)} {...props} />
));
Alert.displayName = "Alert";

const AlertTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(
({ className, ...props }, ref) => (
<h5 ref={ref} className={cn("mb-1 font-medium leading-none tracking-tight", className)} {...props} />
)
);
AlertTitle.displayName = "AlertTitle";

const AlertDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
({ className, ...props }, ref) => (
<div ref={ref} className={cn("text-sm [&_p]:leading-relaxed", className)} {...props} />
)
);
AlertDescription.displayName = "AlertDescription";

export { Alert, AlertTitle, AlertDescription };

0 comments on commit ff255a5

Please sign in to comment.