Skip to content

Commit dd3f57e

Browse files
authored
Merge branch 'master' into override-function
2 parents 98f7cfe + bc6fc6a commit dd3f57e

14 files changed

Lines changed: 214 additions & 99 deletions

File tree

backend/Pipfile

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[[source]]
2+
url = "https://pypi.org/simple"
3+
verify_ssl = true
4+
name = "pypi"
5+
6+
[packages]
7+
8+
[dev-packages]
9+
10+
[requires]
11+
python_version = "3.11"

backend/degree/views.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -446,13 +446,11 @@ def is_satisfied(rule):
446446
legal=legal,
447447
)
448448
if just_created:
449-
f.save()
450449
f.rules.set(selected_rules)
451450
f.unselected_rules.set(unselected_rules)
452451
else:
453452
f.rules.add(selected_rules)
454453
f.unselected_rules.add(unselected_rules)
455-
f.save()
456454

457455
for rule in selected_rules:
458456
satisfied_lookup[rule.id] += 1

frontend/alert/pages/_document.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export default class MyDocument extends Document {
4040
href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css"
4141
/>
4242
<link rel="icon" href="/favicon.ico" />
43+
<script src="https://status.pennlabs.org/banner.js" defer />
4344
</Head>
4445
<body>
4546
<Main />

frontend/degree-plan/components/OnboardingPanels/CreateWithTranscriptPanel.tsx

Lines changed: 24 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
SetStateAction,
55
useCallback,
66
useEffect,
7+
useMemo,
78
useState,
89
} from "react";
910
import {
@@ -89,34 +90,29 @@ export default function CreateWithTranscriptPanel({
8990
// Will likely change in the future!
9091
useEffect(() => {
9192
if (degreeID) {
92-
const courses = [];
93-
for (let semester of scrapedCourses) {
94-
const formattedSemester: { sem: String; courses: String[] } = {
95-
sem: "",
96-
courses: [],
97-
};
98-
let rawSem = semester.sem;
93+
const courses = scrapedCourses.map((semester: any) => {
94+
const rawSem = semester.sem;
95+
let sem: string;
9996
if (rawSem === "_TRAN") {
100-
formattedSemester.sem = "_TRAN";
97+
sem = "_TRAN";
10198
} else {
102-
let formattedSem = rawSem.match(/(\d+)/)[0];
103-
104-
if (rawSem.includes("spring")) formattedSem += "A";
105-
else if (rawSem.includes("summer")) formattedSem += "B";
106-
else formattedSem += "C";
107-
formattedSemester.sem = formattedSem;
99+
const year = rawSem.match(/(\d+)/)[0];
100+
const suffix = rawSem.includes("spring") ? "A" : rawSem.includes("summer") ? "B" : "C";
101+
sem = year + suffix;
108102
}
109-
formattedSemester.courses = semester.courses.map((course: String) =>
110-
course.replace(" ", "-").toUpperCase()
111-
);
112-
courses.push(formattedSemester);
113-
}
103+
return {
104+
sem,
105+
courses: semester.courses.map((course: string) =>
106+
course.replace(" ", "-").toUpperCase()
107+
),
108+
};
109+
});
114110

115-
if (courses.length == 0) {
111+
if (courses.length === 0) {
116112
setShowOnboardingModal(false);
117113
} else {
118114
postFetcher(`/api/degree/onboard-from-transcript/${degreeID}`, {
119-
courses: courses,
115+
courses,
120116
}).then((r) => setShowOnboardingModal(false));
121117
}
122118
}
@@ -152,7 +148,7 @@ export default function CreateWithTranscriptPanel({
152148
JSON.stringify(semesters)
153149
);
154150
}
155-
postFetcher(`/api/degree/degreeplans/${_new.id}/degrees`, {
151+
await postFetcher(`/api/degree/degreeplans/${_new.id}/degrees`, {
156152
degree_ids: majors.map((m) => m.value.id),
157153
}); // add degree
158154
setActiveDegreeplan(_new);
@@ -200,13 +196,12 @@ export default function CreateWithTranscriptPanel({
200196
};
201197
}, [options]);
202198

203-
const startingYearOptions = getYearOptions()?.startYears;
204-
const graduationYearOptions = getYearOptions()?.gradYears;
199+
const { startYears: startingYearOptions, gradYears: graduationYearOptions } = getYearOptions();
205200

206-
const majorOptionsCallback = useCallback(() => {
207-
const majorOptions = getMajorOptions(degrees, schools, startingYear?.value ?? null);
208-
return majorOptions;
209-
}, [schools, startingYear]);
201+
const majorOptions = useMemo(
202+
() => getMajorOptions(degrees, schools, startingYear?.value ?? null),
203+
[degrees, schools, startingYear]
204+
);
210205

211206
return (
212207
<CenteredFlexContainer>
@@ -285,7 +280,7 @@ export default function CreateWithTranscriptPanel({
285280
<FieldWrapper>
286281
<Label required>Major(s)</Label>
287282
<Select
288-
options={majorOptionsCallback()}
283+
options={majorOptions}
289284
value={majors}
290285
onChange={(selectedOptions) => setMajors([...selectedOptions])}
291286
isClearable

frontend/degree-plan/pages/OnboardingPage.tsx

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,9 @@ const OnboardingPage = ({
4545
>(`/api/degree/degrees`);
4646

4747
// TRANSCRIPT PARSING
48-
const total = useRef<Record<number, ParsedText[]>>({});
48+
const total = useRef<Record<number, ParsedText>>({});
4949
const addText = (items: any[], index: number) => {
50-
const parsed = parseItems(items);
51-
total.current[index] = total.current[index] ?? [];
52-
total.current[index].push(parsed);
50+
total.current[index] = parseItems(items);
5351

5452
// If all pages have been read, begin to parse text from transcript
5553
if (Object.keys(total.current).length === numPages) {
@@ -59,11 +57,9 @@ const OnboardingPage = ({
5957
.sort((a, b) => a - b);
6058

6159
sortedPageIndexes.forEach((pageIndex) => {
62-
const pageEntries = total.current[pageIndex];
63-
if (!pageEntries) return;
64-
pageEntries.forEach((pageText) => {
65-
all = all.concat(flattenParsedText(pageText));
66-
});
60+
const pageEntry = total.current[pageIndex];
61+
if (!pageEntry) return;
62+
all = all.concat(flattenParsedText(pageEntry));
6763
});
6864

6965
const {
@@ -73,7 +69,6 @@ const OnboardingPage = ({
7369
detectedMajorsOptions,
7470
} = parseTranscript(all, degrees);
7571
setScrapedCourses(scrapedCourses);
76-
console.log(scrapedCourses);
7772
setStartingYear({
7873
value: startYear,
7974
label: startYear,

frontend/degree-plan/utils/parseUtils.ts

Lines changed: 16 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -180,18 +180,14 @@ const detectMajors = (
180180
possibleDegrees: DegreeOption[] | undefined
181181
) => {
182182
const detectedMajorsOptions = [];
183+
const justMajorNames = possibleDegrees ? possibleDegrees.map((el) => el.label) : [];
183184
for (let i in detectedMajors) {
184185
let m =
185186
detectedMajors[i] +
186187
(detectedConcentrations.length > parseInt(i)
187188
? detectedConcentrations[i]
188189
: "");
189190
if (!detectedMajors[i]?.includes("undeclared") && possibleDegrees) {
190-
let justMajorNames = possibleDegrees.reduce((acc: any, el: any) => {
191-
acc.push(el.label);
192-
return acc;
193-
}, []);
194-
195191
let closestMajor =
196192
m == "computer science "
197193
? "Computer Science - No Concentration (2024)"
@@ -246,31 +242,23 @@ export const parseTranscript = (
246242
}
247243
}
248244

249-
const separatedCourses = Object.entries(courseToSem).reduce(
250-
(acc, [course, sem]) => {
251-
const trimmedSem = sem.trim();
252-
if (!acc[trimmedSem]) acc[trimmedSem] = [];
253-
acc[trimmedSem].push(course);
254-
return acc;
255-
},
256-
{} as { [key: string]: string[] }
257-
);
258-
259-
const formattedSeparatedCourses = Object.entries(separatedCourses).map(
260-
([sem, courses]) => ({
261-
sem,
262-
courses,
263-
})
245+
const formattedSeparatedCourses = Object.values(
246+
Object.entries(courseToSem).reduce(
247+
(acc, [course, sem]) => {
248+
const trimmedSem = sem.trim();
249+
if (!acc[trimmedSem]) acc[trimmedSem] = { sem: trimmedSem, courses: [] };
250+
acc[trimmedSem].courses.push(course);
251+
return acc;
252+
},
253+
{} as { [key: string]: { sem: string; courses: string[] } }
254+
)
264255
);
265256

266-
// Scrape start year and infer grad year
267-
let years = formattedSeparatedCourses.map(
268-
(e: { sem: string; courses: string[] }, i: number) => {
269-
return parseInt(e.sem.replace(/\D/g, ""));
270-
}
271-
);
272-
years.shift();
273-
startYear = Math.min(...years);
257+
// Scrape start year and infer grad year (skip _TRAN which yields NaN)
258+
const years = formattedSeparatedCourses
259+
.map(({ sem }) => parseInt(sem.replace(/\D/g, "")))
260+
.filter((y) => !isNaN(y));
261+
startYear = years.length ? Math.min(...years) : 0;
274262

275263
let possibleDegrees = getMajorOptions(degrees, tempSchools, startYear);
276264
let detectedMajorsOptions = detectMajors(

frontend/plan/components/map/Map.tsx

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import { Location } from "../../types";
66
interface MapProps {
77
locations: Location[];
88
zoom: number;
9+
focusedLocation?: { lat: number; lng: number } | null;
10+
onMarkerClick?: (loc: { lat: number; lng: number }) => void;
911
}
1012

1113
function toRadians(degrees: number): number {
@@ -82,32 +84,47 @@ function separateOverlappingPoints(points: Location[], offset = 0.0001) {
8284
interface InnerMapProps {
8385
locations: Location[];
8486
center: [number, number]
87+
focusedLocation?: { lat: number; lng: number } | null;
88+
onMarkerClick?: (loc: { lat: number; lng: number }) => void;
8589
}
8690

8791
// need inner child component to use useMap hook to run on client
88-
function InnerMap({ locations, center } :InnerMapProps) {
92+
function InnerMap({ locations, center, focusedLocation, onMarkerClick } :InnerMapProps) {
8993
const map = useMap();
9094

9195
useEffect(() => {
92-
map.flyTo({ lat: center[0], lng: center[1]})
93-
}, [center[0], center[1]])
96+
if (!map) return;
97+
const target = focusedLocation ?? { lat: center[0], lng: center[1] };
98+
map.flyTo(target);
99+
}, [center[0], center[1], focusedLocation?.lat, focusedLocation?.lng]);
94100

95101
return (
96102
<>
97103
<TileLayer
98104
// @ts-ignore
99-
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
100-
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
105+
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors &copy; <a href="https://carto.com/attributions">CARTO</a>'
106+
url="https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png"
107+
maxZoom={19}
101108
/>
102-
{separateOverlappingPoints(locations).map(({ lat, lng, color }, i) => (
103-
<Marker key={i} lat={lat} lng={lng} color={color}/>
109+
{separateOverlappingPoints(locations).map(({ lat, lng, color, id, start, end, room }, i) => (
110+
<Marker
111+
key={i}
112+
lat={lat}
113+
lng={lng}
114+
color={color}
115+
id={id}
116+
start={start}
117+
end={end}
118+
room={room}
119+
onClick={onMarkerClick ? () => onMarkerClick({ lat, lng }) : undefined}
120+
/>
104121
))}
105122
</>
106123
)
107124

108125
}
109126

110-
function Map({ locations, zoom }: MapProps) {
127+
function Map({ locations, zoom, focusedLocation, onMarkerClick }: MapProps) {
111128
const center = getGeographicCenter(locations);
112129

113130
return (
@@ -119,7 +136,12 @@ function Map({ locations, zoom }: MapProps) {
119136
scrollWheelZoom={true}
120137
style={{ height: "100%", width: "100%" }}
121138
>
122-
<InnerMap locations={locations} center={center}/>
139+
<InnerMap
140+
locations={locations}
141+
center={center}
142+
focusedLocation={focusedLocation}
143+
onMarkerClick={onMarkerClick}
144+
/>
123145
</MapContainer>
124146
);
125147
};

frontend/plan/components/map/MapCourseItem.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,10 @@ interface CartSectionProps {
100100
end: number;
101101
room: string;
102102
hasLocationData: boolean;
103+
lat?: number | null;
104+
lng?: number | null;
103105
focusSection: (id: string) => void;
106+
focusLocation?: (loc: { lat: number; lng: number }) => void;
104107
}
105108

106109
function MapCourseItem({
@@ -110,7 +113,10 @@ function MapCourseItem({
110113
end,
111114
room,
112115
hasLocationData,
116+
lat,
117+
lng,
113118
focusSection,
119+
focusLocation,
114120
}: CartSectionProps) {
115121
return (
116122
<CourseCartItem
@@ -120,8 +126,11 @@ function MapCourseItem({
120126
$isMobile={isMobile}
121127
$hasLocationData={hasLocationData}
122128
onClick={() => {
123-
const split = id.split("-");
124-
focusSection(`${split[0]}-${split[1]}`);
129+
const [courseDepartment, courseCode] = id.split("-");
130+
focusSection(`${courseDepartment}-${courseCode}`);
131+
if (focusLocation && lat != null && lng != null) {
132+
focusLocation({ lat, lng });
133+
}
125134
}}
126135
>
127136
<Dot $color={color} />

0 commit comments

Comments
 (0)