Skip to content

Commit a3377f1

Browse files
authored
Merge pull request #340 from NIAEFEUP/feature/switchSidebarPosition
Feature: Button that allows the user to switch the side where the sidebar is displayed
2 parents 18a61f0 + fd5b5be commit a3377f1

File tree

7 files changed

+125
-46
lines changed

7 files changed

+125
-46
lines changed

src/App.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ const App = () => {
8181
<Layout location={page.location} title={page.location} liquid={page.liquid}>
8282
<div>
8383
<page.element />
84-
<Toaster />
84+
<Toaster/>
8585
</div>
8686
</Layout>
8787
}
+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { createContext, useContext, useState } from "react";
2+
import PropTypes from "prop-types";
3+
4+
type SidebarContextType = {
5+
sidebarPosition: 'left' | 'right';
6+
toggleSidebarPosition: () => void;
7+
};
8+
9+
const SidebarContext = createContext<SidebarContextType | undefined>(undefined);
10+
11+
export const SidebarProvider = ({ children }: { children: JSX.Element }) => {
12+
const [sidebarPosition, setSidebarPosition] = useState<'left' | 'right'>(() => {
13+
const storedPosition = window.localStorage.getItem("sidebar-position");
14+
return storedPosition === "left" || storedPosition === "right" ? storedPosition : "right";
15+
});
16+
17+
const toggleSidebarPosition = () => {
18+
setSidebarPosition((prev) => {
19+
const newPosition = prev === "right" ? "left" : "right";
20+
window.localStorage.setItem("sidebar-position", newPosition);
21+
return newPosition;
22+
});
23+
};
24+
25+
return (
26+
<SidebarContext.Provider value={{ sidebarPosition, toggleSidebarPosition }}>
27+
{children}
28+
</SidebarContext.Provider>
29+
);
30+
};
31+
32+
SidebarProvider.propTypes = {
33+
children: PropTypes.node.isRequired,
34+
}
35+
36+
export const useSidebarContext = () => {
37+
const context = useContext(SidebarContext);
38+
if (!context) {
39+
throw new Error('useSidebarContext must be used within a SidebarProvider')
40+
}
41+
return context
42+
}

src/components/planner/Sidebar.tsx

+45-2
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,43 @@ import OptionsController from './sidebar/OptionsController'
44
import SelectedOptionController from './sidebar/SelectedOptionController'
55
import CoursesController from './sidebar/CoursesController'
66
import MultipleOptionsContext from '../../contexts/MultipleOptionsContext'
7+
import { useSidebarContext } from '../layout/SidebarPosition'
8+
import { ArrowsRightLeftIcon, TrashIcon } from '@heroicons/react/24/outline'
9+
import { Button } from '../ui/button'
710

811
/**
912
* Sidebar with all the main schedule interactions
1013
*/
1114
const Sidebar = () => {
12-
const { multipleOptions, selectedOption } = useContext(MultipleOptionsContext);
15+
const { multipleOptions, selectedOption, setMultipleOptions } = useContext(MultipleOptionsContext);
16+
const { toggleSidebarPosition } = useSidebarContext();
17+
18+
const noClassesPicked = !multipleOptions[selectedOption]?.course_options.some(
19+
(option) => option.picked_class_id !== null
20+
);
21+
22+
const eraseClasses = () => {
23+
const currentOption = multipleOptions[selectedOption];
24+
25+
const updatedCourseOptions = currentOption.course_options.map(courseOption => ({
26+
...courseOption,
27+
picked_class_id: null,
28+
locked: false,
29+
}));
30+
31+
const updatedMultipleOptions = [...multipleOptions];
32+
updatedMultipleOptions[selectedOption] = {
33+
...currentOption,
34+
course_options: updatedCourseOptions,
35+
};
36+
37+
setMultipleOptions(updatedMultipleOptions);
38+
};
39+
1340

1441
return (
1542
<div className="lg:min-h-adjusted order-2 col-span-12 flex min-h-min flex-col justify-between rounded-md bg-lightest px-3 py-3 dark:bg-dark lg:col-span-3 2xl:px-4 2xl:py-4">
16-
<div className="space-y-2">
43+
<div className="flex-grow space-y-2">
1744
<div className="relative flex flex-row flex-wrap items-center justify-center gap-x-2 gap-y-2 lg:justify-start">
1845
<SessionController />
1946
<OptionsController />
@@ -23,6 +50,22 @@ const Sidebar = () => {
2350
<CoursesController />
2451
</div>
2552
</div>
53+
<footer className="mt-4 gap-x-1 border-white-300 pt-3 text-center flex items-end justify-end">
54+
<Button
55+
onClick={eraseClasses}
56+
variant="icon"
57+
className={`bg-lightish text-darkish gap-1.5 ${noClassesPicked ? 'opacity-50 pointer-events-none' : ''}`}
58+
>
59+
<TrashIcon className="h-5 w-5" />
60+
<span>Limpar</span>
61+
</Button>
62+
<button title='Mudar o lado da Sidebar'
63+
onClick={toggleSidebarPosition}
64+
className="hidden md:flex items-center justify-center gap-2 w-[48px] h-[40px] bg-primary hover:opacity-80 dark:text-white rounded-md p-1 text-gray text-sm"
65+
>
66+
<ArrowsRightLeftIcon className="h-5 w-5 text-white dark:text-white" />
67+
</button>
68+
</footer>
2669
</div>
2770
)
2871
}

src/components/planner/sidebar/CoursesController.tsx

-32
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,14 @@
11
import { useContext } from 'react'
22
import { PlannerClassSelector }from './CoursesController/PlannerClassSelector'
33
import CourseContext from '../../../contexts/CourseContext'
4-
import MultipleOptionsContext from '../../../contexts/MultipleOptionsContext'
5-
import { TrashIcon } from "@heroicons/react/24/outline"
64
import { NoMajorSelectedSVG } from '../../svgs'
75
import { Button } from '../../ui/button'
86

97
const CoursesController = () => {
108
const { pickedCourses, setUcsModalOpen } = useContext(CourseContext);
11-
const { multipleOptions, selectedOption, setMultipleOptions } = useContext(MultipleOptionsContext);
129

1310
const noCoursesPicked = pickedCourses.length === 0;
1411

15-
const eraseClasses = () => {
16-
const currentOption = multipleOptions[selectedOption];
17-
18-
const updatedCourseOptions = currentOption.course_options.map(courseOption => ({
19-
...courseOption,
20-
picked_class_id: null,
21-
locked: false,
22-
}));
23-
24-
const updatedMultipleOptions = [...multipleOptions];
25-
updatedMultipleOptions[selectedOption] = {
26-
...currentOption,
27-
course_options: updatedCourseOptions,
28-
};
29-
30-
setMultipleOptions(updatedMultipleOptions);
31-
};
3212

3313
return (
3414
<div className={`flex ${noCoursesPicked ? 'h-max justify-center' : ''} w-full flex-col gap-4 px-0 py-2`}>
@@ -53,18 +33,6 @@ const CoursesController = () => {
5333
))
5434
)}
5535

56-
{!noCoursesPicked && (
57-
<div className="mt-4 flex justify-end">
58-
<Button
59-
onClick={eraseClasses}
60-
variant="icon"
61-
className="bg-lightish text-darkish gap-1.5"
62-
>
63-
<TrashIcon className="h-5 w-5" />
64-
<span>Limpar</span>
65-
</Button>
66-
</div>
67-
)}
6836

6937
</div>
7038
)

src/components/planner/sidebar/CoursesController/ClassItem.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,4 @@ const ClassItem = ({ course_id, classInfo, onSelect, onMouseEnter, onMouseLeave
7474
)
7575
}
7676

77-
export default ClassItem
77+
export default ClassItem

src/components/planner/sidebar/sessionController/CoursePicker.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ import CoursePickerContext from '../../../../contexts/coursePicker/CoursePickerC
1212

1313
//TODO: absolute imports with @
1414

15+
1516
const CoursePicker = () => {
17+
1618
const {
1719
coursesStorage,
1820
setCoursesInfo,

src/pages/TimeTableSelector.tsx

+34-10
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ import { useEffect, useContext } from 'react'
33
import { Sidebar } from '../components/planner'
44
import { Major } from '../@types'
55
import MajorContext from '../contexts/MajorContext'
6+
import { useSidebarContext } from '../components/layout/SidebarPosition'
7+
import { SidebarProvider } from '../components/layout/SidebarPosition'
68
import PlannerSchedule from '../components/planner/schedule/PlannerSchedule'
79

810
const TimeTableSelectorPage = () => {
9-
const {setMajors} = useContext(MajorContext);
11+
const { setMajors } = useContext(MajorContext);
1012

1113
// fetch majors when component is ready
1214
useEffect(() => {
@@ -17,17 +19,39 @@ const TimeTableSelectorPage = () => {
1719
}, [])
1820

1921
return (
20-
<div className="grid w-full grid-cols-12 gap-x-4 gap-y-4 px-4 py-4">
21-
{/* Schedule Preview */}
22-
<div className="lg:min-h-adjusted order-1 col-span-12 min-h-min rounded bg-lightest px-3 py-3 dark:bg-dark lg:col-span-9 2xl:px-5 2xl:py-5">
23-
<div className="h-full w-full">
24-
<PlannerSchedule />
25-
</div>
26-
</div>
22+
<SidebarProvider>
23+
<Content />
24+
</SidebarProvider>
25+
);
26+
};
27+
28+
const Content = () => {
29+
const { sidebarPosition } = useSidebarContext();
2730

28-
<Sidebar />
31+
return (
32+
<div className="grid w-full grid-cols-12 gap-x-4 gap-y-4 px-4 py-4">
33+
{sidebarPosition === 'left' ? (
34+
<>
35+
<div className='col-span-12 lg:col-span-3 min-h'>
36+
<Sidebar />
37+
</div>
38+
<div className='col-span-12 lg:col-span-9 min-h rounded-md bg-lightest px-3 py-3 dark:bg-dark 2xl:px-5 2xl:py-5'>
39+
<PlannerSchedule />
40+
</div>
41+
</>
42+
) : (
43+
<>
44+
<div className='col-span-12 lg:col-span-9 min-h rounded-md bg-lightest px-3 py-3 dark:bg-dark 2xl:px-5 2xl:py-5'>
45+
<PlannerSchedule />
46+
</div>
47+
<div className='col-span-12 lg:col-span-3 min-h'>
48+
<Sidebar />
49+
</div>
50+
</>
51+
)}
2952
</div>
3053
)
3154
}
3255

33-
export default TimeTableSelectorPage;
56+
57+
export default TimeTableSelectorPage;

0 commit comments

Comments
 (0)