Skip to content

Commit

Permalink
Merge pull request #29 from pennlabs/aag/req-panel
Browse files Browse the repository at this point in the history
Link backend and requirements panel + fix drag/drop v0
  • Loading branch information
AaDalal authored Feb 12, 2024
2 parents f2082f6 + 0856cd6 commit 8cb7baf
Show file tree
Hide file tree
Showing 13 changed files with 272 additions and 142 deletions.
4 changes: 2 additions & 2 deletions backend/degree/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,9 @@ class DegreePlanDetailSerializer(serializers.ModelSerializer):
)
degrees = DegreeDetailSerializer(read_only=True, many=True)
degree_ids = serializers.PrimaryKeyRelatedField(
many=True,
required=False,
write_only=True,
source="degree",
source="degrees",
queryset=Degree.objects.all(),
help_text="The degree_id this degree plan belongs to.",
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ const CoursePlanned = ({course, semesterIndex, removeCourse, highlightReqId, set
<div onClick={() => showCourseDetail(course)}>
{course.id}
</div>
<RemoveCourseButton hidden={mouseOver} onClick={() => removeCourse(course)}>
<RemoveCourseButton hidden={!mouseOver} onClick={() => removeCourse(course)}>
<GrayIcon><i className="fas fa-times"></i></GrayIcon>
</RemoveCourseButton>
</PlannedCourseContainer>
Expand Down
24 changes: 18 additions & 6 deletions frontend/degree-plan/components/FourYearPlan/CoursesPlanned.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,35 @@


import { useEffect, useState } from "react";
import { Ref, useEffect, useState } from "react";
import CoursePlanned, { PlannedCourseContainer } from "./CoursePlanned";
import styled from "@emotion/styled";

const PlannedCoursesContainer = styled.div`
flex-grow: 1;
display: flex;
flex-direction: column;
gap: .5rem;
`;

const CoursesPlanned = ({courses, semesterIndex, removeCourse, showCourseDetail, highlightReqId, className}: any) => {
interface CoursesPlannedProps {
courses: any;
semesterIndex: number;
removeCourse: any;
showCourseDetail: any;
highlightReqId: any; // TODO: should not be anys
className: string;
dropRef: Ref<React.ReactNode>;
}

const CoursesPlanned = ({courses, semesterIndex, removeCourse, showCourseDetail, highlightReqId, className, dropRef}: any) => {
const [courseOpen, setCourseOpen] = useState(false);

return (
<PlannedCoursesContainer className={className}>
{courses.length === 0 ?
<PlannedCourseContainer/>
: courses.map((course: any) =>
<CoursePlanned course={course} highlightReqId={highlightReqId} semesterIndex={semesterIndex} removeCourse={removeCourse} courseOpen={courseOpen} setCourseOpen={setCourseOpen} showCourseDetail={showCourseDetail}/>
{courses.map((course: any) =>
<CoursePlanned course={course} highlightReqId={highlightReqId} semesterIndex={semesterIndex} removeCourse={removeCourse} setCourseOpen={setCourseOpen} showCourseDetail={showCourseDetail}/>
)}
<PlannedCourseContainer ref={dropRef}/>
</PlannedCoursesContainer>
)
}
Expand Down
26 changes: 16 additions & 10 deletions frontend/degree-plan/components/FourYearPlan/PlanPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const ShowStatsButton = ({ showStats, setShowStats }: { showStats: boolean, setS
)
}

const PlanPanelHeader = styled.div`
export const PanelHeader = styled.div`
display: flex;
justify-content: space-between;
background-color:'#DBE2F5';
Expand All @@ -38,13 +38,13 @@ const PlanPanelHeader = styled.div`
flex-grow: 0;
`;

const OverflowSemesters = styled(Semesters)`
overflow-y: scroll;
export const PanelBody = styled.div`
overflow-y: auto;
flex-grow: 1;
padding: 1rem;
`;

const PlanPanelContainer = styled.div`
export const PanelContainer = styled.div`
display: flex;
flex-direction: column;
height: 100%;
Expand All @@ -71,6 +71,7 @@ const ModalInteriorWrapper = styled.div<{ $row?: boolean }>`
align-items: center;
padding: 1rem;
gap: .5rem;
text-align: center;
`;

const ModalInput = styled.input`
Expand Down Expand Up @@ -131,7 +132,10 @@ const ModalInterior = ({ modalObject, modalKey, close, create, rename, remove }:
return (
<ModalInteriorWrapper>
<p>Are you sure you want to remove this degree plan?</p>
<ModalButton onClick={() => remove(modalObject.id)}>Remove</ModalButton>
<ModalButton onClick={() => {
remove(modalObject.id)
close();
}}>Remove</ModalButton>
</ModalInteriorWrapper>
);
}
Expand Down Expand Up @@ -199,7 +203,7 @@ const PlanPanel = ({ setActiveDegreeplanId, activeDegreeplan, degreeplans, isLoa

return (
<>
<PlanPanelContainer>
<PanelContainer>
{modalKey && <ModalContainer
title={getModalTitle(modalKey)}
close={() => setModalKey(null)}
Expand All @@ -217,7 +221,7 @@ const PlanPanel = ({ setActiveDegreeplanId, activeDegreeplan, degreeplans, isLoa
/>
</ModalContainer>
}
<PlanPanelHeader>
<PanelHeader>
<SelectListDropdown
itemType="degree plan"
active={activeDegreeplan}
Expand All @@ -242,10 +246,12 @@ const PlanPanel = ({ setActiveDegreeplanId, activeDegreeplan, degreeplans, isLoa
}}
/>
<ShowStatsButton showStats={showStats} setShowStats={setShowStats} />
</PlanPanelHeader>
</PanelHeader>
{/** map to semesters */}
<OverflowSemesters semesters={semesters} setSemesters={setSemesters} showStats={showStats} addCourse={addCourse}/>
</PlanPanelContainer>
<PanelBody>
<Semesters semesters={semesters} setSemesters={setSemesters} showStats={showStats} addCourse={addCourse}/>
</PanelBody>
</PanelContainer>
</>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,9 @@ interface DropdownButton {
onClick: () => void;
makeActive: () => void;
mutators: {
copy: () => void;
remove: (() => void);
rename: (() => void);
copy?: () => void;
remove?: (() => void);
rename?: (() => void);
};
}

Expand Down Expand Up @@ -247,9 +247,9 @@ interface SelectListDropdownProps<T extends DBObject,> {
selectItem: (id: T["id"]) => void;
getItemName: (item: T) => string;
mutators: {
copy: (item: T) => void;
remove: (item: T) => void;
rename: (item: T) => void;
copy?: (item: T) => void;
remove?: (item: T) => void;
rename?: (item: T) => void;
create: () => void;
};
}
Expand Down Expand Up @@ -318,9 +318,9 @@ const SelectListDropdown = <T extends DBObject,>({
onClick={() => selectItem(data.id)}
text={getItemName(data)}
mutators={{
copy: () => copy(data),
remove: () => remove(data),
rename: () => rename(data)
copy: copy && (() => copy(data)),
remove: remove && (() => remove(data)),
rename: rename && (() => rename(data))
}}
/>
);
Expand Down
2 changes: 1 addition & 1 deletion frontend/degree-plan/components/FourYearPlan/Semester.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ const Semester = ({semester, addCourse, index, highlightReqId, removeCourseFromS
{semester.name}
</SemesterLabel>
<SemesterContent ref={ref}>
<FlexCoursesPlanned courses={semester.courses} highlightReqId={highlightReqId} semesterIndex={index} removeCourse={removeCourse} showCourseDetail={showCourseDetail}/>
<FlexCoursesPlanned dropRef={drop} courses={semester.courses} highlightReqId={highlightReqId} semesterIndex={index} removeCourse={removeCourse} showCourseDetail={showCourseDetail}/>
{showStats && <FlexStats courses={semester.courses}/>}
</SemesterContent>
<CreditsLabel>
Expand Down
4 changes: 2 additions & 2 deletions frontend/degree-plan/components/Requirements/Major.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Requirement from "./Requirement"
import Rule from "./Requirement"
import Icon from '@mdi/react';
import { mdiTrashCanOutline } from '@mdi/js';
import { mdiReorderHorizontal } from '@mdi/js';
Expand Down Expand Up @@ -34,7 +34,7 @@ const Major = ({major, setSearchClosed}: any) => {
</div>
{!collapsed &&
<div className='ms-2 mt-2'>
{major.requirements.map((requirement: any) => ( <Requirement key={requirement.id} requirement={requirement} setSearchClosed={setSearchClosed}/>))}
{major.requirements.map((requirement: any) => ( <Rule key={requirement.id} requirement={requirement} setSearchClosed={setSearchClosed}/>))}
</div>}
</div>
)
Expand Down
2 changes: 1 addition & 1 deletion frontend/degree-plan/components/Requirements/QObj.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ const RootQObj = ({query, reqId}) => {
break;
case 'attributes__code__in':
const attrs = stripChar(value, '[', ']');
queryComponent = `attribute must be one of`;
queryComponent = `attribute must be one of `;
// for (const attr in attrs) {
queryComponent += stripChar(attrs, '\'');
// }
Expand Down
128 changes: 85 additions & 43 deletions frontend/degree-plan/components/Requirements/ReqPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,57 +1,99 @@
import Icon from '@mdi/react';
import { mdiNoteEditOutline, mdiArrowLeft, mdiPlus } from '@mdi/js';
import { useEffect, useState } from 'react';
import { PanelTopBar } from '@/pages/FourYearPlanPage';
import SelectListDropdown from '../FourYearPlan/SelectListDropdown';
import SwitchFromList from '../FourYearPlan/SwitchFromList';
import Requirement from './Requirement';
import axios from 'axios';
import Rule from './Requirement';
import { Degree, DegreePlan } from '@/types';
import styled from '@emotion/styled';
import { PanelBody, PanelContainer, PanelHeader } from '@/components/FourYearPlan/PlanPanel'
import { useSWRCrud } from '@/hooks/swrcrud';
import { set, update } from 'lodash';
import { useSWRConfig } from 'swr';

const requirementDropdownListStyle = {
maxHeight: '90%',
width: '100%',
overflowY: 'scroll',
overflowY: 'auto',
paddingRight: '15px',
paddingLeft: '15px',
marginTop: '10px'
}

const ReqPanel = ({majors, currentMajor, highlightReqId, setCurrentMajor, setMajors, setSearchClosed, setDegreeModalOpen, handleSearch, setHighlightReqId}:any) => {
const [editMode, setEditMode] = useState(false);
const [majorData, setMajorData] = useState({});

useEffect(() => {
const getMajor = async () => {
// const res = await axios.get(`/degree/degrees/${currentMajor.id}`);
// console.log(res.data);
// setMajorData(res.data);
// return;
}
if (currentMajor.id) getMajor();
}, [currentMajor])
const EmptyPanelContainer = styled.div`
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100%;
padding: 2rem;
text-align: center;
`;

const EmptyPanelImage = styled.img`
max-width: min(60%, 40vw);
`;

const EmptyPanel = () => {
return (
<EmptyPanelContainer>
<EmptyPanelImage src="/images/empty-state-cal.svg" />
There's nothing here so far... add a degree to get started!
</EmptyPanelContainer>
)
}

interface ReqPanelProps {
activeDegreePlan: DegreePlan | null;
isLoading: boolean;
}
const ReqPanel = ({activeDegreePlan, isLoading, highlightReqId, setSearchClosed, handleSearch, setHighlightReqId}: ReqPanelProps) => {
const degrees = activeDegreePlan?.degrees;
const [activeDegreeId, setActiveDegreeId] = useState<Degree["id"] | undefined>(undefined);
const [activeDegree, setActiveDegree] = useState<Degree | undefined>(undefined);
useEffect(() => {
if (!activeDegreeId && degrees?.length) {
setActiveDegreeId(degrees[0].id);
}
}, [activeDegreeId, activeDegreePlan])

useEffect(() => {
if (activeDegreeId && degrees) {
setActiveDegree(degrees.find(degree => degree.id === activeDegreeId));
}
})

return(
<>
<PanelTopBar>
<SelectListDropdown
allItems={[]}
active={undefined}
selectItem={setCurrentMajor}
itemType={"major or degree"}
mutators={{
copy: () => {},
remove: () => {},
rename: () => {},
create: () => {}
}}
/>
</PanelTopBar>
<div style={requirementDropdownListStyle}>
{majorData && majorData.rules && majorData.rules.map((requirement: any) => (
<Requirement requirement={requirement} setSearchClosed={setSearchClosed} parent={null} handleSearch={handleSearch} setHighlightReqId={setHighlightReqId} highlightReqId={highlightReqId} key={requirement.id}/>
))}
</div>
</>
);
const { update: updateDegreeplan } = useSWRCrud<DegreePlan>('/api/degree/degreeplans/');

return(
<PanelContainer>
<PanelHeader>
<SelectListDropdown
allItems={degrees || []}
active={activeDegree}
selectItem={(id: Degree["id"]) => setActiveDegreeId(id)}
getItemName={(degree: Degree) => `${degree.major}, ${degree.degree}`}
itemType={"major or degree"}
mutators={{
remove: (degree) => {
updateDegreeplan(
{degree_ids: activeDegreePlan?.degree_ids?.filter(id => id !== degree.id) || []},
activeDegreePlan?.id
)
if (degree.id === activeDegreeId) setActiveDegreeId(undefined);
},
create: () => updateDegreeplan(
{degree_ids: [...(activeDegreePlan?.degree_ids || []), 1867]}, // TODO: this is a placeholder, we need to add a new degree
activeDegreePlan?.id
)?.then(() => setActiveDegreeId(1867)),
}}
/>
</PanelHeader>
<PanelBody>
{activeDegree?.rules?.map((rule: any) => (
<Rule rule={rule} setSearchClosed={setSearchClosed} handleSearch={handleSearch} setHighlightReqId={setHighlightReqId} highlightReqId={highlightReqId} key={rule.id}/>
))
|| <EmptyPanel />
}
</PanelBody>
</PanelContainer>
);
}
export default ReqPanel;
Loading

0 comments on commit 8cb7baf

Please sign in to comment.