Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(sidebar): Add Sandbox notes area #759

Merged
merged 13 commits into from
Nov 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 100 additions & 0 deletions packages/frontend/components/Sidebar/SandboxArea.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import {
Box,
VStack,
Flex,
Image,
Text,
Textarea,
TextareaProps,
} from "@chakra-ui/react";
import { forwardRef, useEffect, useState } from "react";
import ResizeTextarea from "react-textarea-autosize";
import { toast } from "react-toastify";

const AutoResizeTextarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
(props, ref) => {
return (
<Textarea
minH="unset"
w="100%"
ref={ref}
minRows={4}
as={ResizeTextarea}
maxRows={10}
{...props}
/>
);
}
);

AutoResizeTextarea.displayName = "AutoResizeTextarea";

interface SandboxAreaProps {
planId: string | number;
}

type StoredNotes = {
[planId: string]: string;
};

function getStoredNotes(): StoredNotes {
const storedNotes = localStorage.getItem("notes");
if (!storedNotes) {
return {};
}

return JSON.parse(storedNotes);
}

function setStoredNotes(notes: StoredNotes) {
try {
localStorage.setItem("notes", JSON.stringify(notes));
} catch (e) {
toast.error("Maximum local storage quota exceed. Too many plans.");
}
}

function getPlanNote(planId: string): string {
const notesObject = getStoredNotes();
return notesObject[planId] || "";
}

export const SandboxArea: React.FC<SandboxAreaProps> = ({ planId }) => {
const [notes, setNotes] = useState<string>("");

const handleNewNotes = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
if (!planId) return;
setNotes(e.target.value);
const notesObj = getStoredNotes();
notesObj[planId] = e.target.value;
setStoredNotes(notesObj);
};

useEffect(() => {
if (!planId) return;
setNotes(getPlanNote(String(planId)));
}, [planId]);

return (
<Box backgroundColor="white" pt="6" pb="6" px="3">
<VStack align="left" px="4">
<Flex mb="3">
<Image src="/sandbox_logo.svg" alt="sandbox logo" mr="2" />
<Text color="primary.blue.dark.main" fontSize="sm" fontWeight="bold">
Sandbox Area
</Text>
</Flex>
<Text color="primary.blue.dark.main" fontSize="sm" fontWeight="bold">
Notes
</Text>
<AutoResizeTextarea
placeholder="notes here!"
resize="vertical"
height="initial"
value={notes}
onChange={handleNewNotes}
/>
</VStack>
</Box>
);
};
102 changes: 6 additions & 96 deletions packages/frontend/components/Sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
import { Badge, Box, Flex, Heading, Link, Stack, Text } from "@chakra-ui/react";
import { Link, Stack, Text } from "@chakra-ui/react";
import {
MajorValidationError,
MajorValidationResult,
PlanModel,
ScheduleCourse2,
} from "@graduate/common";
import { memo, PropsWithChildren, useEffect, useRef, useState } from "react";
import { DraggableScheduleCourse } from "../ScheduleCourse";
import { memo, useEffect, useRef, useState } from "react";
import SidebarSection from "./SidebarSection";
import {
getAllCoursesFromPlan,
getSectionError,
getAllCoursesInMajor,
BETA_MAJOR_TOOLTIP_MSG,
} from "../../utils";
import {
handleApiClientError,
Expand All @@ -27,10 +25,9 @@ import {
WorkerPostInfo,
} from "../../validation-worker/worker-messages";
import { useFetchCourses, useMajor } from "../../hooks";
import { HelperToolTip } from "../Help";
import NUPathSection from "./NUPathSection";
import DropdownWarning from "./DropdownWarning";
import { NUPathEnum } from "@graduate/common";
import SidebarContainer from "./SidebarContainer";

export enum SidebarValidationStatus {
Loading = "Loading",
Expand Down Expand Up @@ -205,6 +202,7 @@ const Sidebar: React.FC<SidebarProps> = memo(
creditsToTake={major.totalCreditsRequired}
renderCoopBlock
renderBetaMajorBlock={major.metadata?.verified !== true}
planId={selectedPlan.id}
>
{courseData && (
<>
Expand Down Expand Up @@ -269,8 +267,9 @@ export const NoMajorSidebar: React.FC<NoMajorSidebarProps> = ({
creditsTaken={creditsTaken}
renderCoopBlock
renderDropdownWarning={false}
planId={selectedPlan.id}
>
<Stack px="md">
<Stack px="md" mb="3">
<Text>
A major has not been selected for this plan. Please select one if you
would like to see major requirements. If we do not support your major,
Expand All @@ -294,99 +293,10 @@ export const NoMajorSidebar: React.FC<NoMajorSidebarProps> = ({
);
};

interface SidebarContainerProps {
title: string;
subtitle?: string;
creditsTaken?: number;
creditsToTake?: number;
renderCoopBlock?: boolean;
renderBetaMajorBlock?: boolean;
renderDropdownWarning?: boolean;
}

export const NoPlanSidebar: React.FC = () => {
return <SidebarContainer title="No Plan Selected" />;
};

const SidebarContainer: React.FC<PropsWithChildren<SidebarContainerProps>> = ({
title,
subtitle,
creditsTaken,
creditsToTake,
renderCoopBlock,
renderBetaMajorBlock,
renderDropdownWarning = true,
children,
}) => {
return (
<Box
pt="xl"
backgroundColor="neutral.50"
borderRight="1px"
borderRightColor="neutral.200"
minH="100%"
>
<Box px="md" pb="md">
<Box pb="sm">
{renderBetaMajorBlock && (
<Flex alignItems="center" pb="sm">
<Badge
borderColor="red"
borderWidth="1px"
variant="outline"
colorScheme="red"
fontWeight="bold"
fontSize="sm"
borderRadius="md"
mr="sm"
>
BETA MAJOR
</Badge>
<HelperToolTip label={BETA_MAJOR_TOOLTIP_MSG} />
</Flex>
)}
<Flex alignItems="center" columnGap="2xs">
<Heading
as="h1"
fontSize="2xl"
color="primary.blue.dark.main"
fontWeight="bold"
>
{title}
</Heading>
</Flex>
{subtitle && (
<Text fontSize="sm" color="primary.blue.dark.main">
{subtitle}
</Text>
)}
</Box>
{renderDropdownWarning && <DropdownWarning />}
{creditsTaken !== undefined && (
<Flex mb="sm" alignItems="baseline" columnGap="xs">
<Text
fontSize="2xl"
color="primary.blue.dark.main"
fontWeight="bold"
>
{creditsTaken}
{creditsToTake !== undefined && `/${creditsToTake}`}
</Text>
<Text color="primary.blue.dark.main">Credits Completed</Text>
</Flex>
)}
{renderCoopBlock && (
<DraggableScheduleCourse
scheduleCourse={COOP_BLOCK}
isDisabled={false}
/>
)}
</Box>
{children}
</Box>
);
};

// We need to manually set the display name like this because
// of how we're using memo above.
Sidebar.displayName = "Sidebar";
Expand Down
97 changes: 97 additions & 0 deletions packages/frontend/components/Sidebar/SidebarContainer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { Box, Flex, Badge, Heading, Text } from "@chakra-ui/react";
import { PropsWithChildren } from "react";
import { BETA_MAJOR_TOOLTIP_MSG } from "../../utils";
import { HelperToolTip } from "../Help";
import { DraggableScheduleCourse } from "../ScheduleCourse";
import DropdownWarning from "./DropdownWarning";
import { COOP_BLOCK } from "./Sidebar";
import { SandboxArea } from "./SandboxArea";

interface SidebarContainerProps {
title: string;
subtitle?: string;
creditsTaken?: number;
creditsToTake?: number;
renderCoopBlock?: boolean;
renderBetaMajorBlock?: boolean;
renderDropdownWarning?: boolean;
planId?: string | number;
}

const SidebarContainer: React.FC<PropsWithChildren<SidebarContainerProps>> = ({
title,
subtitle,
creditsTaken,
creditsToTake,
renderCoopBlock,
renderBetaMajorBlock,
renderDropdownWarning = true,
planId,
children,
}) => {
return (
<Box pt="xl" borderRight="1px" borderRightColor="neutral.200" minH="100%">
<Box px="md" pb="md">
<Box pb="sm">
{renderBetaMajorBlock && (
<Flex alignItems="center" pb="sm">
<Badge
borderColor="red"
borderWidth="1px"
variant="outline"
colorScheme="red"
fontWeight="bold"
fontSize="sm"
borderRadius="md"
mr="sm"
>
BETA MAJOR
</Badge>
<HelperToolTip label={BETA_MAJOR_TOOLTIP_MSG} />
</Flex>
)}
<Flex alignItems="center" columnGap="2xs">
<Heading
as="h1"
fontSize="2xl"
color="primary.blue.dark.main"
fontWeight="bold"
>
{title}
</Heading>
</Flex>
{subtitle && (
<Text fontSize="sm" color="primary.blue.dark.main">
{subtitle}
</Text>
)}
</Box>
{renderDropdownWarning && <DropdownWarning />}
{creditsTaken !== undefined && (
<Flex mb="sm" alignItems="baseline" columnGap="xs">
<Text
fontSize="2xl"
color="primary.blue.dark.main"
fontWeight="bold"
>
{creditsTaken}
{creditsToTake !== undefined && `/${creditsToTake}`}
</Text>
<Text color="primary.blue.dark.main">Credits Completed</Text>
</Flex>
)}
{renderCoopBlock && (
<DraggableScheduleCourse
scheduleCourse={COOP_BLOCK}
isDisabled={false}
/>
)}
</Box>

{children}

{planId && <SandboxArea planId={planId} />}
KobeZ123 marked this conversation as resolved.
Show resolved Hide resolved
</Box>
);
};
export default SidebarContainer;
1 change: 1 addition & 0 deletions packages/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"react-error-boundary": "^3.1.4",
"react-hook-form": "^7.33.0",
"react-select": "^5.7.2",
"react-textarea-autosize": "^8.5.4",
"react-toastify": "^8.2.0",
"swr": "^1.3.0"
},
Expand Down
8 changes: 8 additions & 0 deletions packages/frontend/public/sandbox_logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading