Skip to content

Commit

Permalink
Merge pull request #38 from Smart-Home-Guard/feat/room-diagram
Browse files Browse the repository at this point in the history
Feat/room diagram
  • Loading branch information
quannhg authored May 21, 2024
2 parents 77d8737 + 0661e0b commit 61e0128
Show file tree
Hide file tree
Showing 7 changed files with 645 additions and 49 deletions.
11 changes: 10 additions & 1 deletion app/home/fire-alert/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { components } from "@/types/openapi-spec";
import { apiClient } from "@/lib/apiClient";
import { useCallback, useEffect, useState } from "react";
import { toast } from "@/components/ui/use-toast";
import { useIsNavBarCollapsed } from "@/store";

type NotificationStatus = "SAFE" | "DANGEROUS" | "IDLE";
const LightKindList = [56];
Expand All @@ -30,6 +31,8 @@ export default function HomePage() {
components["schemas"]["ResponseRoom"][]
>([]);

const { isNavBarCollapsed } = useIsNavBarCollapsed();

const Header = () => {
const buzzerList: { componentId: number; deviceId: number }[] = [];
userRoomData.map(({ devices }) =>
Expand Down Expand Up @@ -451,7 +454,13 @@ export default function HomePage() {
return (
<div className="flex flex-col gap-8">
<Header />
<div className="pl-24 flex flex-row gap-8 overflow-x-auto">
<div
className={`pl-24 pr-24 max-w-[${
isNavBarCollapsed ? 1850 : 1630
}px] w-[${
isNavBarCollapsed ? 1850 : 1630
}px] flex flex-row gap-8 overflow-auto`}
>
{userRoomData.map(({ name, devices }) =>
devices.map(({ id, components }) => (
<RoomStatusInformation
Expand Down
50 changes: 29 additions & 21 deletions app/home/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@ import { NavigationBar } from "./navbar";
import { redirect, usePathname } from "next/navigation";
import { Fragment, useEffect, useTransition } from "react";
import { useLoggedInStore } from "@/store";
import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbSeparator } from "@/components/ui/breadcrumb";
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbSeparator,
} from "@/components/ui/breadcrumb";
import { FlameIcon, HomeIcon } from "lucide-react";

export default function HomeLayout({
Expand Down Expand Up @@ -38,7 +44,11 @@ function AppBreadcrumb() {
const pathname = usePathname();
const pathComponents = pathname.split("/").slice(1);

const itemMap: { [index: string]: Record<"href", string> & Record<"icon", typeof HomeIcon> & Record<"name", string> } = {
const itemMap: {
[index: string]: Record<"href", string> &
Record<"icon", typeof HomeIcon> &
Record<"name", string>;
} = {
home: {
icon: HomeIcon,
href: "/home/",
Expand All @@ -54,26 +64,24 @@ function AppBreadcrumb() {
return (
<Breadcrumb className="mb-8">
<BreadcrumbList>
{
pathComponents.map((p) => {
const Icon = itemMap[p].icon;
const href = itemMap[p].href;
const name = itemMap[p].name;
{pathComponents.map((p) => {
const Icon = itemMap[p].icon;
const href = itemMap[p].href;
const name = itemMap[p].name;

return (
<Fragment key={p}>
<BreadcrumbItem className="flex items-center">
<Icon size={18} />
<BreadcrumbLink href={href}>{name}</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator>
<p className="text-neutral">/</p>
</BreadcrumbSeparator>
</Fragment>
)
})
}
return (
<Fragment key={p}>
<BreadcrumbItem className="flex items-center">
<Icon size={18} />
<BreadcrumbLink href={href}>{name}</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator>
<p className="text-neutral">/</p>
</BreadcrumbSeparator>
</Fragment>
);
})}
</BreadcrumbList>
</Breadcrumb>
)
);
}
72 changes: 49 additions & 23 deletions app/home/navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,18 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { useEmailStore, useJwtStore, useLoggedInStore } from "@/store";
import { TooltipContent, TooltipPortal, TooltipProvider, TooltipTrigger } from "@radix-ui/react-tooltip";
import {
useEmailStore,
useIsNavBarCollapsed,
useJwtStore,
useLoggedInStore,
} from "@/store";
import {
TooltipContent,
TooltipPortal,
TooltipProvider,
TooltipTrigger,
} from "@radix-ui/react-tooltip";
import { Tooltip } from "@/components/ui/tooltip";

export function NavigationTab({
Expand Down Expand Up @@ -117,17 +127,31 @@ export function AvatarPane({ className = "" }: { className?: string }) {
export function NavigationBar() {
const [isHoverSidebar, setIsHoverSidebar] = useState<boolean>(false);
const [hoverTimeout, setHoverTimeout] = useState<any>(undefined);
const [isCollapsed, setIsCollapsed] = useState<boolean>(false);
const {
isNavBarCollapsed: isCollapsed,
setIsNavBarCollapsed: setIsCollapsed,
} = useIsNavBarCollapsed();

return (
<div
className="min-h-screen p-0 m-0"
onMouseEnter={() => { hoverTimeout && clearTimeout(hoverTimeout); setIsHoverSidebar(true); }}
onMouseLeave={() => setHoverTimeout(setTimeout(() => setIsHoverSidebar(false), 500))}
onMouseEnter={() => {
hoverTimeout && clearTimeout(hoverTimeout);
setIsHoverSidebar(true);
}}
onMouseLeave={() =>
setHoverTimeout(setTimeout(() => setIsHoverSidebar(false), 500))
}
>
<div className={`w-${isCollapsed ? "[0]" :"[220px]"} transform transition-all duration-300 ease-in-out`} />
<div
className={`w-${
isCollapsed ? "[0]" : "[220px]"
} transform transition-all duration-300 ease-in-out`}
/>
<nav
className={`h-full bg-primary-slightly-dark text-neutral-very-light w-[220px] shadow-xl shadow-primary-slightly-light fixed transform transition duration-300 ease-in-out ${isCollapsed ? "-translate-x-full" : ""}`}
className={`h-full bg-primary-slightly-dark text-neutral-very-light w-[220px] shadow-xl shadow-primary-slightly-light fixed transform transition duration-300 ease-in-out ${
isCollapsed ? "-translate-x-full" : ""
}`}
>
<div className="bg-primary p-16 shadow-md">
<Link
Expand All @@ -147,22 +171,24 @@ export function NavigationBar() {
<div className="flex flex-col h-full">
<AvatarPane />
<NavigationPane />
</div>
<div className={`transition-opacity duration-100 opacity-${isHoverSidebar || isCollapsed ? "100" : "0"}`}>
<div
className="fixed top-1/2 left-[220px] border-l-primary-dark border-l-[8px] border-t-[transparent] border-t-[10px] border-b-[transparent] border-b-[10px] w-[0] h-64 bg-[transparent] z-40 rounded-none"
/>
<button
className="fixed top-1/2 left-[218px] w-[8px] h-64 bg-[transparent] z-40 rounded-none text-neutral-light"
onClick={() => setIsCollapsed(!isCollapsed)}
>
{
isCollapsed ?
<ChevronRight className="w-12"/> :
<ChevronLeft className="w-12"/>
}
</button>
</div>
</div>
<div
className={`transition-opacity duration-100 opacity-${
isHoverSidebar || isCollapsed ? "100" : "0"
}`}
>
<div className="fixed top-1/2 left-[220px] border-l-primary-dark border-l-[8px] border-t-[transparent] border-t-[10px] border-b-[transparent] border-b-[10px] w-[0] h-64 bg-[transparent] z-40 rounded-none" />
<button
className="fixed top-1/2 left-[218px] w-[8px] h-64 bg-[transparent] z-40 rounded-none text-neutral-light"
onClick={() => setIsCollapsed(!isCollapsed)}
>
{isCollapsed ? (
<ChevronRight className="w-12" />
) : (
<ChevronLeft className="w-12" />
)}
</button>
</div>
</nav>
</div>
);
Expand Down
104 changes: 104 additions & 0 deletions app/home/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import { useToast } from "@/components/ui/use-toast";
import { Label } from "@/components/ui/label";
import { Input } from "@/components/ui/input";
import { MultiSelect, OptionType } from "@/components/ui/multiselect";
import { RoomChart as RoomHistoryChart } from "./roomChart";

const MetricHistoryChart = dynamic(() => import("./metricLineChart"), {
ssr: false,
Expand Down Expand Up @@ -171,6 +172,108 @@ function RoomStatusSection({

const [openDialog, setOpenDialog] = useState<boolean>(false);

function RoomChart({ roomName }: { roomName: string }) {
const [metricType, setMetricType] = useState<MetricType>("co");

const metricTypeMap: Record<
MetricType,
{
title: string;

subtitle: string;
}
> = {
co: {
title: "CO concentration",

subtitle: "By ppm",
},

flame: {
title: "Heat",

subtitle: "By °C",
},

gas: {
title: "Gas concentration",

subtitle: "By ppm",
},

smoke: {
title: "Smoke concentration",

subtitle: "By ppm",
},
};

return (
<Card className="w-full bg-[#FFFFFF] border-none drop-shadow-md">
<CardContent className="justify-center items-start p-16">
<DropdownMenu modal={false}>
<DropdownMenuTrigger asChild className="p-16 bg-neutral-dark">
<Button
variant="outline"
className="text-neutral-very-light flex items-center justify-between"
>
<p>Open</p>

<ChevronDownIcon size={18} color="white" />
</Button>
</DropdownMenuTrigger>

<DropdownMenuPortal>
<DropdownMenuContent className="w-full z-50 bg-neutral-slightly-light shadow-lg p-4 text-left rounded-lg">
<DropdownMenuRadioGroup
value={metricType}
onValueChange={setMetricType as any}
>
<DropdownMenuRadioItem
value="co"
className="hover:bg-primary hover:text-neutral-light rounded-lg p-4"
>
CO Concentration
</DropdownMenuRadioItem>

<DropdownMenuRadioItem
value="smoke"
className="hover:bg-primary hover:text-neutral-light rounded-lg p-4"
>
Smoke
</DropdownMenuRadioItem>

<DropdownMenuRadioItem
value="flame"
className="hover:bg-primary hover:text-neutral-light rounded-lg p-4"
>
Flame
</DropdownMenuRadioItem>

<DropdownMenuRadioItem
value="gas"
className="hover:bg-primary hover:text-neutral-light rounded-lg p-4"
>
Gas Leak
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenuPortal>
</DropdownMenu>

<div className="p-16 col-span-3">
<RoomHistoryChart
roomName={roomName}
data={{}}
{...metricTypeMap[metricType]}
metricType={metricType}
/>
</div>
</CardContent>
</Card>
);
}

async function onSubmit(newRoom: z.infer<typeof formSchema>) {
const res = await apiClient.POST("/api/rooms/", {
body: {
Expand Down Expand Up @@ -339,6 +442,7 @@ function RoomStatusSection({
</div>
))}
</div>
<RoomChart roomName={room.name} />
</DialogContent>
</Dialog>
))}
Expand Down
Loading

0 comments on commit 61e0128

Please sign in to comment.