Scaffolded admin page
This commit is contained in:
@@ -10,6 +10,7 @@ import CreatePostPage from "./pages/CreatePostPage";
|
|||||||
import EditPostPage from "./pages/EditPostPage";
|
import EditPostPage from "./pages/EditPostPage";
|
||||||
import SchedulePage from "./pages/SchedulePage";
|
import SchedulePage from "./pages/SchedulePage";
|
||||||
import EventsPage from "./pages/EventsPage";
|
import EventsPage from "./pages/EventsPage";
|
||||||
|
import AdministratorSpringboard from "./pages/AdministratorSpringboard";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
@@ -19,13 +20,13 @@ function App() {
|
|||||||
<Route element={<SignInPage />} path="/signin" />
|
<Route element={<SignInPage />} path="/signin" />
|
||||||
<Route element={<SpringboardPage />} path="/springboard" />
|
<Route element={<SpringboardPage />} path="/springboard" />
|
||||||
<Route element={<ManageUserAccountPage />} path="/manage-account" />
|
<Route element={<ManageUserAccountPage />} path="/manage-account" />
|
||||||
|
<Route element={<AdministratorSpringboard />} path="/admin" />
|
||||||
|
|
||||||
<Route element={<CommunityPage />} path="/community" />
|
<Route element={<CommunityPage />} path="/community" />
|
||||||
<Route element={<CreatePostPage />} path="/createPost" />
|
<Route element={<CreatePostPage />} path="/createPost" />
|
||||||
<Route element={<EditPostPage />} path="/editPost/:id" />
|
<Route element={<EditPostPage />} path="/editPost/:id" />
|
||||||
<Route element={<SchedulePage />} path="/schedule" />
|
<Route element={<SchedulePage />} path="/schedule" />
|
||||||
<Route element={<EventsPage />} path="/events" />
|
<Route element={<EventsPage />} path="/events" />
|
||||||
|
|
||||||
</Routes>
|
</Routes>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
212
client/src/components/AdministratorNavigationPanel.tsx
Normal file
212
client/src/components/AdministratorNavigationPanel.tsx
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
import {
|
||||||
|
Avatar,
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
CircularProgress,
|
||||||
|
ScrollShadow,
|
||||||
|
} from "@nextui-org/react";
|
||||||
|
import {
|
||||||
|
CalendarDaysIcon,
|
||||||
|
ChartBarIcon,
|
||||||
|
ClipboardDocumentListIcon,
|
||||||
|
ClockIcon,
|
||||||
|
TagIcon,
|
||||||
|
GiftTopIcon,
|
||||||
|
ChatBubbleOvalLeftIcon,
|
||||||
|
ChevronLeftIcon,
|
||||||
|
} from "../icons";
|
||||||
|
import EcoconnectFullLogo from "./EcoconnectFullLogo";
|
||||||
|
import { retrieveUserInformation } from "../security/users";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import config from "../config";
|
||||||
|
import AdministratorNavigationPanelNavigationButton from "./AdministratorNavigationPanelNavigationButton";
|
||||||
|
import EcoconnectLogo from "./EcoconnectLogo";
|
||||||
|
|
||||||
|
export default function AdministratorNavigationPanel() {
|
||||||
|
const [userInformation, setUserInformation] = useState<any>();
|
||||||
|
const [userProfileImageURL, setUserProfileImageURL] = useState("");
|
||||||
|
const [panelVisible, setPanelVisible] = useState(true);
|
||||||
|
const [isScrolled, setIsScrolled] = useState(false);
|
||||||
|
useEffect(() => {
|
||||||
|
retrieveUserInformation().then((value) => {
|
||||||
|
setUserInformation(value);
|
||||||
|
setUserProfileImageURL(
|
||||||
|
`${config.serverAddress}/users/profile-image/${value.id}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
const handleScroll = () => {
|
||||||
|
setIsScrolled(window.scrollY > 10);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener("scroll", handleScroll);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("scroll", handleScroll);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
return (
|
||||||
|
<div className="h-full">
|
||||||
|
<div
|
||||||
|
className={`fixed transition-all top-${isScrolled ? "2" : "10"} left-2`}
|
||||||
|
>
|
||||||
|
<div className="bg-white rounded-full z-40">
|
||||||
|
<Button
|
||||||
|
isIconOnly
|
||||||
|
size="lg"
|
||||||
|
color="primary"
|
||||||
|
variant="flat"
|
||||||
|
radius="full"
|
||||||
|
className="shadow-lg"
|
||||||
|
onPress={() => {
|
||||||
|
setPanelVisible(!panelVisible);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<EcoconnectLogo />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Panel */}
|
||||||
|
<div
|
||||||
|
className={`h-full transition-all z-50 ${
|
||||||
|
panelVisible
|
||||||
|
? "scale-100 opacity-100 w-[300px] px-2"
|
||||||
|
: "w-0 scale-[98%] opacity-0 px-0"
|
||||||
|
}`}
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
className={`fixed h-full transition-all z-50 ${
|
||||||
|
isScrolled ? "pb-2 -mt-8" : "pb-10"
|
||||||
|
} ${
|
||||||
|
panelVisible
|
||||||
|
? "scale-100 opacity-100 w-[300px] p-2"
|
||||||
|
: "w-0 scale-[98%] opacity-0 p-0"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<Card className="h-full w-full">
|
||||||
|
<div className="flex flex-col h-full">
|
||||||
|
<div className="flex flex-row justify-between bg-primary-50 dark:bg-primary-950">
|
||||||
|
<div className="flex flex-col text-right p-4">
|
||||||
|
<EcoconnectFullLogo />
|
||||||
|
<p className="text-2xl text-primary-800 dark:text-primary-100 font-semibold">
|
||||||
|
administrators
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
onPress={() => {
|
||||||
|
setPanelVisible(!panelVisible);
|
||||||
|
}}
|
||||||
|
isIconOnly
|
||||||
|
variant="light"
|
||||||
|
className="rounded-tl-none rounded-br-none"
|
||||||
|
>
|
||||||
|
<div className="rotate-180">
|
||||||
|
<ChevronLeftIcon />
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{userInformation && (
|
||||||
|
<div className="flex flex-col h-full">
|
||||||
|
<ScrollShadow className="h-full">
|
||||||
|
<div className="flex flex-col gap-4 p-4 *:flex *:flex-col">
|
||||||
|
<div>
|
||||||
|
<p className="text-sm font-bold opacity-50 pb-2">
|
||||||
|
Events
|
||||||
|
</p>
|
||||||
|
<AdministratorNavigationPanelNavigationButton
|
||||||
|
text="Events"
|
||||||
|
icon={<CalendarDaysIcon />}
|
||||||
|
onClickRef="#"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-sm font-bold opacity-50 pb-2">
|
||||||
|
Community Forums
|
||||||
|
</p>
|
||||||
|
<AdministratorNavigationPanelNavigationButton
|
||||||
|
text="Posts"
|
||||||
|
icon={<ClipboardDocumentListIcon />}
|
||||||
|
onClickRef="#"
|
||||||
|
/>
|
||||||
|
<AdministratorNavigationPanelNavigationButton
|
||||||
|
text="Tags"
|
||||||
|
icon={<TagIcon />}
|
||||||
|
onClickRef="#"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-sm font-bold opacity-50 pb-2">
|
||||||
|
Bill Contest
|
||||||
|
</p>
|
||||||
|
<AdministratorNavigationPanelNavigationButton
|
||||||
|
text="Ranking"
|
||||||
|
icon={<ChartBarIcon />}
|
||||||
|
onClickRef="#"
|
||||||
|
/>
|
||||||
|
<AdministratorNavigationPanelNavigationButton
|
||||||
|
text="Vouchers"
|
||||||
|
icon={<GiftTopIcon />}
|
||||||
|
onClickRef="#"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-sm font-bold opacity-50 pb-2">
|
||||||
|
Karang Guni
|
||||||
|
</p>
|
||||||
|
<AdministratorNavigationPanelNavigationButton
|
||||||
|
text="Schedules"
|
||||||
|
icon={<CalendarDaysIcon />}
|
||||||
|
onClickRef="#"
|
||||||
|
/>
|
||||||
|
<AdministratorNavigationPanelNavigationButton
|
||||||
|
text="Transactions"
|
||||||
|
icon={<ClockIcon />}
|
||||||
|
onClickRef="#"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-sm font-bold opacity-50 pb-2">Users</p>
|
||||||
|
<AdministratorNavigationPanelNavigationButton
|
||||||
|
text="User Feedbacks"
|
||||||
|
icon={<ChatBubbleOvalLeftIcon />}
|
||||||
|
onClickRef="#"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ScrollShadow>
|
||||||
|
<div className="bg-primary-500 p-1">
|
||||||
|
<Button variant="light" className="h-full w-full p-2">
|
||||||
|
<div className="flex flex-row w-full justify-start">
|
||||||
|
<div className="flex flex-row w-full gap-3 *:my-auto text-white">
|
||||||
|
<Avatar src={userProfileImageURL} />
|
||||||
|
<div className="flex flex-col h-full text-left">
|
||||||
|
<p className="font-semibold">
|
||||||
|
{userInformation.firstName +
|
||||||
|
" " +
|
||||||
|
userInformation.lastName}
|
||||||
|
</p>
|
||||||
|
<p className="text-sm opacity-70">
|
||||||
|
{userInformation.email}
|
||||||
|
</p>
|
||||||
|
<p className="text-sm opacity-70">
|
||||||
|
+65 {userInformation.phoneNumber}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{!userInformation && (
|
||||||
|
<div className="w-full h-full flex flex-col justify-center">
|
||||||
|
<div className="w-full h-full flex flex-row justify-center">
|
||||||
|
<CircularProgress />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { Button } from "@nextui-org/react";
|
||||||
|
import React from "react";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
|
export default function AdministratorNavigationPanelNavigationButton({
|
||||||
|
text,
|
||||||
|
icon,
|
||||||
|
onClickRef,
|
||||||
|
}: {
|
||||||
|
text: string;
|
||||||
|
icon: React.JSX.Element;
|
||||||
|
onClickRef: string;
|
||||||
|
}) {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
variant="light"
|
||||||
|
onPress={() => {
|
||||||
|
navigate(onClickRef);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="flex flex-row gap-2 w-full *:my-auto">
|
||||||
|
<div className="text-primary-500">{icon}</div>
|
||||||
|
<p>{text}</p>
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
9
client/src/components/EcoconnectFullLogo.tsx
Normal file
9
client/src/components/EcoconnectFullLogo.tsx
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
export default function EcoconnectFullLogo() {
|
||||||
|
return (
|
||||||
|
<img
|
||||||
|
src="../../assets/ecoconnectFull.svg"
|
||||||
|
alt="ecoconnect logo"
|
||||||
|
className="h-6 dark:invert dark:hue-rotate-180"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
9
client/src/components/EcoconnectLogo.tsx
Normal file
9
client/src/components/EcoconnectLogo.tsx
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
export default function EcoconnectLogo() {
|
||||||
|
return (
|
||||||
|
<img
|
||||||
|
src="../../assets/ecoconnectLogo.svg"
|
||||||
|
alt="ecoconnect logo"
|
||||||
|
className="h-6 dark:invert dark:hue-rotate-180"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ import config from "../config";
|
|||||||
import { retrieveUserInformation } from "../security/users";
|
import { retrieveUserInformation } from "../security/users";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import EcoconnectFullLogo from "./EcoconnectFullLogo";
|
||||||
|
|
||||||
export default function NavigationBar() {
|
export default function NavigationBar() {
|
||||||
let [userProfileImageURL, setUserProfileImageURL] = useState("");
|
let [userProfileImageURL, setUserProfileImageURL] = useState("");
|
||||||
@@ -69,11 +70,7 @@ export default function NavigationBar() {
|
|||||||
navigate("/");
|
navigate("/");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img
|
<EcoconnectFullLogo />
|
||||||
src="../../assets/ecoconnectFull.svg"
|
|
||||||
alt="ecoconnect logo"
|
|
||||||
className="h-6 dark:invert dark:hue-rotate-180"
|
|
||||||
/>
|
|
||||||
</Button>
|
</Button>
|
||||||
<div className="flex flex-row *:my-auto *:text-primary-800 dark:*:text-primary-100">
|
<div className="flex flex-row *:my-auto *:text-primary-800 dark:*:text-primary-100">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -268,3 +268,160 @@ export const ChatBubbleOvalLeftEllipsisIcon = () => {
|
|||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const ClipboardDocumentListIcon = () => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
strokeWidth={1.5}
|
||||||
|
stroke="currentColor"
|
||||||
|
className="size-6"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
d="M9 12h3.75M9 15h3.75M9 18h3.75m3 .75H18a2.25 2.25 0 0 0 2.25-2.25V6.108c0-1.135-.845-2.098-1.976-2.192a48.424 48.424 0 0 0-1.123-.08m-5.801 0c-.065.21-.1.433-.1.664 0 .414.336.75.75.75h4.5a.75.75 0 0 0 .75-.75 2.25 2.25 0 0 0-.1-.664m-5.8 0A2.251 2.251 0 0 1 13.5 2.25H15c1.012 0 1.867.668 2.15 1.586m-5.8 0c-.376.023-.75.05-1.124.08C9.095 4.01 8.25 4.973 8.25 6.108V8.25m0 0H4.875c-.621 0-1.125.504-1.125 1.125v11.25c0 .621.504 1.125 1.125 1.125h9.75c.621 0 1.125-.504 1.125-1.125V9.375c0-.621-.504-1.125-1.125-1.125H8.25ZM6.75 12h.008v.008H6.75V12Zm0 3h.008v.008H6.75V15Zm0 3h.008v.008H6.75V18Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TagIcon = () => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
strokeWidth={1.5}
|
||||||
|
stroke="currentColor"
|
||||||
|
className="size-6"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
d="M9.568 3H5.25A2.25 2.25 0 0 0 3 5.25v4.318c0 .597.237 1.17.659 1.591l9.581 9.581c.699.699 1.78.872 2.607.33a18.095 18.095 0 0 0 5.223-5.223c.542-.827.369-1.908-.33-2.607L11.16 3.66A2.25 2.25 0 0 0 9.568 3Z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
d="M6 6h.008v.008H6V6Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ChartBarIcon = () => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
strokeWidth={1.5}
|
||||||
|
stroke="currentColor"
|
||||||
|
className="size-6"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
d="M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 0 1 3 19.875v-6.75ZM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 0 1-1.125-1.125V8.625ZM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 0 1-1.125-1.125V4.125Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CalendarDaysIcon = () => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
strokeWidth={1.5}
|
||||||
|
stroke="currentColor"
|
||||||
|
className="size-6"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 0 1 2.25-2.25h13.5A2.25 2.25 0 0 1 21 7.5v11.25m-18 0A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75m-18 0v-7.5A2.25 2.25 0 0 1 5.25 9h13.5A2.25 2.25 0 0 1 21 11.25v7.5m-9-6h.008v.008H12v-.008ZM12 15h.008v.008H12V15Zm0 2.25h.008v.008H12v-.008ZM9.75 15h.008v.008H9.75V15Zm0 2.25h.008v.008H9.75v-.008ZM7.5 15h.008v.008H7.5V15Zm0 2.25h.008v.008H7.5v-.008Zm6.75-4.5h.008v.008h-.008v-.008Zm0 2.25h.008v.008h-.008V15Zm0 2.25h.008v.008h-.008v-.008Zm2.25-4.5h.008v.008H16.5v-.008Zm0 2.25h.008v.008H16.5V15Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ClockIcon = () => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
strokeWidth={1.5}
|
||||||
|
stroke="currentColor"
|
||||||
|
className="size-6"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
d="M12 6v6h4.5m4.5 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const GiftTopIcon = () => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
strokeWidth={1.5}
|
||||||
|
stroke="currentColor"
|
||||||
|
className="size-6"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
d="M12 3.75v16.5M2.25 12h19.5M6.375 17.25a4.875 4.875 0 0 0 4.875-4.875V12m6.375 5.25a4.875 4.875 0 0 1-4.875-4.875V12m-9 8.25h16.5a1.5 1.5 0 0 0 1.5-1.5V5.25a1.5 1.5 0 0 0-1.5-1.5H3.75a1.5 1.5 0 0 0-1.5 1.5v13.5a1.5 1.5 0 0 0 1.5 1.5Zm12.621-9.44c-1.409 1.41-4.242 1.061-4.242 1.061s-.349-2.833 1.06-4.242a2.25 2.25 0 0 1 3.182 3.182ZM10.773 7.63c1.409 1.409 1.06 4.242 1.06 4.242S9 12.22 7.592 10.811a2.25 2.25 0 1 1 3.182-3.182Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ChatBubbleOvalLeftIcon = () => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
strokeWidth={1.5}
|
||||||
|
stroke="currentColor"
|
||||||
|
className="size-6"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
d="M12 20.25c4.97 0 9-3.694 9-8.25s-4.03-8.25-9-8.25S3 7.444 3 12c0 2.104.859 4.023 2.273 5.48.432.447.74 1.04.586 1.641a4.483 4.483 0 0 1-.923 1.785A5.969 5.969 0 0 0 6 21c1.282 0 2.47-.402 3.445-1.087.81.22 1.668.337 2.555.337Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const BarsThreeIcon = () => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
strokeWidth={1.5}
|
||||||
|
stroke="currentColor"
|
||||||
|
className="size-6"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
28
client/src/layouts/administrator.tsx
Normal file
28
client/src/layouts/administrator.tsx
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { Toaster } from "react-hot-toast";
|
||||||
|
import SingaporeAgencyStrip from "../components/SingaporeAgencyStrip";
|
||||||
|
import AdministratorNavigationPanel from "../components/AdministratorNavigationPanel";
|
||||||
|
|
||||||
|
export default function AdministratorLayout({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div className="relative flex flex-col h-full">
|
||||||
|
<SingaporeAgencyStrip />
|
||||||
|
<div className="flex flex-row h-full ">
|
||||||
|
<div className="h-full z-50">
|
||||||
|
<AdministratorNavigationPanel />
|
||||||
|
</div>
|
||||||
|
<main className="flex-grow">{children}</main>
|
||||||
|
</div>
|
||||||
|
<Toaster />
|
||||||
|
|
||||||
|
{/*
|
||||||
|
A div that becomes black in dark mode to cover white color parts
|
||||||
|
of the website when scrolling past the window's original view.
|
||||||
|
*/}
|
||||||
|
<div className="fixed -z-50 dark:bg-black inset-0 w-full h-full"></div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
111
client/src/pages/AdministratorSpringboard.tsx
Normal file
111
client/src/pages/AdministratorSpringboard.tsx
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import AdministratorLayout from "../layouts/administrator";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { getTimeOfDay } from "../utilities";
|
||||||
|
import { retrieveUserInformation } from "../security/users";
|
||||||
|
import UserProfilePicture from "../components/UserProfilePicture";
|
||||||
|
import { Button, Card } from "@nextui-org/react";
|
||||||
|
import { PencilSquareIcon } from "../icons";
|
||||||
|
import EcoconnectFullLogo from "../components/EcoconnectFullLogo";
|
||||||
|
|
||||||
|
export default function AdministratorSpringboard() {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
let accessToken = localStorage.getItem("accessToken");
|
||||||
|
if (!accessToken) {
|
||||||
|
navigate("/signin");
|
||||||
|
}
|
||||||
|
let [userInformation, setUserInformation] = useState<any>();
|
||||||
|
let timeOfDay = getTimeOfDay();
|
||||||
|
|
||||||
|
let greeting = "";
|
||||||
|
if (timeOfDay === 0) {
|
||||||
|
greeting = "Good morning";
|
||||||
|
} else if (timeOfDay === 1) {
|
||||||
|
greeting = "Good afternoon";
|
||||||
|
} else if (timeOfDay === 2) {
|
||||||
|
greeting = "Good evening";
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
retrieveUserInformation()
|
||||||
|
.then((response) => {
|
||||||
|
setUserInformation(response);
|
||||||
|
})
|
||||||
|
.catch((_) => {
|
||||||
|
navigate("/signin");
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}, []);
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{userInformation && (
|
||||||
|
<AdministratorLayout>
|
||||||
|
<div className="flex flex-col w-full pb-2">
|
||||||
|
<div className="flex flex-row justify-between p-8 pt-14 *:my-auto">
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<p className="text-3xl font-bold">
|
||||||
|
{greeting}, {userInformation.firstName}.
|
||||||
|
</p>
|
||||||
|
<div className="flex flex-row gap-2 *:my-auto">
|
||||||
|
<p className="text-xl">A staff member of</p>
|
||||||
|
<EcoconnectFullLogo />
|
||||||
|
</div>
|
||||||
|
<p className="text-primary-500">{userInformation.email}</p>
|
||||||
|
<Button
|
||||||
|
className="w-max"
|
||||||
|
size="sm"
|
||||||
|
variant="flat"
|
||||||
|
color="primary"
|
||||||
|
startContent={
|
||||||
|
<div className="scale-80">
|
||||||
|
<PencilSquareIcon />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
onPress={() => {
|
||||||
|
navigate("/manage-account/");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Manage your account
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<UserProfilePicture
|
||||||
|
userId={userInformation.id}
|
||||||
|
editable={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row justify-stretch *:w-full *:h-56 w-full p-4 pt-0 gap-4"></div>
|
||||||
|
<Card className="w-full bg-primary-500 p-8 text-white rounded-r-none">
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<p className="font-bold text-4xl">Statistics Overview</p>
|
||||||
|
<div className="h-[500px] w-full bg-primary-600 rounded-2xl flex flex-row p-6">
|
||||||
|
<div className="w-60 flex flex-col justify-between">
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<p className="text-2xl">User Count</p>
|
||||||
|
<p className="opacity-70">(past 30 days)</p>
|
||||||
|
</div>
|
||||||
|
<p className="text-lg">
|
||||||
|
Total: <span className="font-bold">2139</span> users
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="w-full h-full bg-white rounded-xl">
|
||||||
|
{/* TODO: Graph */}
|
||||||
|
<p className="text-black">GRAPH HERE</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="h-[500px] w-full bg-primary-600 rounded-2xl flex flex-row p-6">
|
||||||
|
<div className="w-60 flex flex-col justify-between">
|
||||||
|
<p className="text-2xl">Population Distribution</p>
|
||||||
|
</div>
|
||||||
|
<div className="w-full h-full bg-white rounded-xl">
|
||||||
|
{/* TODO: Graph */}
|
||||||
|
<p className="text-black">GRAPH HERE</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</AdministratorLayout>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user