Skip to content

Commit 031030b

Browse files
committed
more cleaning
1 parent 6e84419 commit 031030b

4 files changed

Lines changed: 164 additions & 178 deletions

File tree

features/audit/audit-provider.tsx

Lines changed: 62 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,6 @@ import LoadingPage from "./components/loading-page";
2929
import {
3030
addPlannedCourse as addCourse,
3131
moveCourseToSemester,
32-
removePlannedCourse as removeCourse,
33-
wipePlannedCourses,
3432
} from "./audit-mutations";
3533

3634
type SemesterInfo = Record<StringSemester, Course[]>;
@@ -161,95 +159,72 @@ export function AuditContextProvider({
161159
};
162160
}, [currentAuditId]);
163161

164-
async function persist(
165-
auditId: string,
166-
updated: CachedAuditData,
167-
): Promise<void> {
168-
await saveAuditData(auditId, updated);
169-
setAuditData(updated);
170-
}
171-
172-
async function addPlannedCourse(
173-
course: PlannedCourseOutline,
174-
requirementTitle: string,
175-
ruleTitle: string,
176-
): Promise<CourseId | null> {
177-
if (!auditData || !currentAuditId) return null;
178-
const result = addCourse(auditData, course, requirementTitle, ruleTitle);
179-
if (!result) return null;
180-
await persist(currentAuditId, result.audit);
181-
return result.courseId;
182-
}
183-
184-
async function removePlannedCourse(courseId: CourseId): Promise<boolean> {
185-
if (!auditData || !currentAuditId) return false;
186-
const updated = removeCourse(auditData, courseId);
187-
if (!updated) return false;
188-
await persist(currentAuditId, updated);
189-
return true;
190-
}
191-
192-
async function wipeAllPlannedCourses(): Promise<number> {
193-
if (!auditData || !currentAuditId) return 0;
194-
const result = wipePlannedCourses(auditData);
195-
if (result.removed) await persist(currentAuditId, result.audit);
196-
return result.removed;
197-
}
198-
199-
async function moveCourseToNewSemester(
200-
courseId: CourseId,
201-
semester: StringSemester,
202-
): Promise<boolean> {
203-
if (!auditData || !currentAuditId) return false;
204-
const updated = moveCourseToSemester(auditData, courseId, semester);
205-
if (!updated) return false;
206-
await persist(currentAuditId, updated);
207-
return true;
208-
}
162+
const value = useMemo<AuditContextValue>(() => {
163+
const persist = async (auditId: string, updated: CachedAuditData) => {
164+
await saveAuditData(auditId, updated);
165+
setAuditData(updated);
166+
};
209167

210-
async function renameAuditTitle(
211-
auditId: string,
212-
title: string,
213-
): Promise<boolean> {
214-
const cleanTitle = title.trim();
215-
if (!cleanTitle) return false;
216-
const updatedHistory = await renameAudit(auditId, cleanTitle);
217-
if (!updatedHistory) return false;
218-
setHistory(updatedHistory);
219-
return true;
220-
}
168+
// currentAuditId and history are guaranteed non-null past the loading
169+
// guard below, which is the only path that renders this provider's value.
170+
return {
171+
sections,
172+
history: history as AuditHistoryData,
173+
semesters,
174+
currentAuditId: currentAuditId as string,
175+
currentAudit,
176+
currentAuditName,
177+
progresses,
178+
courseMap,
179+
getCourseById: (id) => {
180+
const course = courseMap[id];
181+
if (!course) throw new Error(`Course ${id} not found`);
182+
return course;
183+
},
184+
setCurrentAuditId: (id) => {
185+
window.history.pushState({}, "", `?auditId=${id}`);
186+
setCurrentAuditIdState(id);
187+
updateLastAuditId(id);
188+
},
189+
renameAuditTitle: async (auditId, title) => {
190+
const cleanTitle = title.trim();
191+
if (!cleanTitle) return false;
192+
const updatedHistory = await renameAudit(auditId, cleanTitle);
193+
if (!updatedHistory) return false;
194+
setHistory(updatedHistory);
195+
return true;
196+
},
197+
moveCourseToNewSemester: async (courseId, semester) => {
198+
if (!auditData || !currentAuditId) return false;
199+
const updated = moveCourseToSemester(auditData, courseId, semester);
200+
if (!updated) return false;
201+
await persist(currentAuditId, updated);
202+
return true;
203+
},
204+
addPlannedCourse: async (course, requirementTitle, ruleTitle) => {
205+
if (!auditData || !currentAuditId) return null;
206+
const result = addCourse(auditData, course, requirementTitle, ruleTitle);
207+
if (!result) return null;
208+
await persist(currentAuditId, result.audit);
209+
return result.courseId;
210+
},
211+
};
212+
}, [
213+
sections,
214+
history,
215+
semesters,
216+
currentAuditId,
217+
currentAudit,
218+
currentAuditName,
219+
progresses,
220+
courseMap,
221+
auditData,
222+
updateLastAuditId,
223+
]);
221224

222225
if (!loaded || !currentAuditId || !history) return <LoadingPage />;
223226

224-
return (
225-
<AuditContext.Provider
226-
value={{
227-
sections,
228-
history,
229-
semesters,
230-
currentAuditId,
231-
currentAudit,
232-
currentAuditName,
233-
setCurrentAuditId: (id) => {
234-
window.history.pushState({}, "", `?auditId=${id}`);
235-
setCurrentAuditIdState(id);
236-
updateLastAuditId(id);
237-
},
238-
renameAuditTitle,
239-
moveCourseToNewSemester,
240-
progresses,
241-
getCourseById: (id) => {
242-
const course = courseMap[id];
243-
if (!course) throw new Error(`Course ${id} not found`);
244-
return course;
245-
},
246-
courseMap,
247-
addPlannedCourse,
248-
}}
249-
>
250-
{children}
251-
</AuditContext.Provider>
252-
);
227+
return <AuditContext.Provider value={value}>{children}</AuditContext.Provider>;
253228
}
254229

255230
export function useAuditContext(): AuditContextValue {

features/catalog/components/course-add-modal.tsx

Lines changed: 11 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,6 @@ import { useAuditContext } from "@/features/audit/audit-provider";
2323
import { useCourseModalContext } from "../course-modal-provider";
2424
import CourseCard from "@/features/planner/components/course-card";
2525

26-
type RecommendationScope = {
27-
requirementTitle?: string;
28-
ruleTitle?: string;
29-
};
30-
31-
interface CourseAddModalProps {
32-
isOpen: boolean;
33-
onClose: () => void;
34-
recommendedCourses?: CatalogCourse[];
35-
recommendationScope?: RecommendationScope | null;
36-
isLoading?: boolean;
37-
}
38-
3926
interface CourseSearchData {
4027
searchQuery: string;
4128
department: string;
@@ -261,20 +248,14 @@ function CourseSearchContent({
261248
);
262249
}
263250

264-
interface FulfillingCoursesContentProps {
265-
courses: CatalogCourse[];
266-
recommendationScope?: RecommendationScope | null;
267-
isLoading?: boolean;
268-
onClose: () => void;
269-
}
270-
271-
function FulfillingCoursesContent({
272-
courses,
273-
recommendationScope,
274-
isLoading = false,
275-
onClose,
276-
}: FulfillingCoursesContentProps) {
251+
function FulfillingCoursesContent() {
277252
const { addPlannedCourse } = useAuditContext();
253+
const {
254+
recommendedCourses: courses,
255+
recommendationScope,
256+
isLoadingRecommendedCourses: isLoading,
257+
closeModal: onClose,
258+
} = useCourseModalContext();
278259
const displayedCourses = dedupeCatalogCoursesByCode(courses);
279260
const [query, setQuery] = useState("");
280261
const [pickedId, setPickedId] = useState<number | null>(null);
@@ -467,19 +448,14 @@ export function CourseSearchPanel() {
467448
);
468449
}
469450

470-
export default function CourseAddModal({
471-
isOpen,
472-
onClose,
473-
recommendedCourses = [],
474-
recommendationScope,
475-
isLoading = false,
476-
}: CourseAddModalProps) {
451+
export default function CourseAddModal() {
452+
const { isOpen, closeModal } = useCourseModalContext();
477453
if (!isOpen) return null;
478454

479455
return (
480456
<div
481457
className="fixed inset-0 z-50 flex items-center justify-center transition-all duration-200 bg-black/50 opacity-100"
482-
onClick={onClose}
458+
onClick={closeModal}
483459
>
484460
<div
485461
className="bg-background rounded-md border border-dap-border shadow-2xl w-full max-w-[550px] max-h-[90vh] mx-4 transform transition-all duration-200 overflow-hidden scale-100 opacity-100"
@@ -489,12 +465,7 @@ export default function CourseAddModal({
489465
<h2 className="text-3xl leading-none font-bold text-text mb-8">
490466
Fulfilling courses
491467
</h2>
492-
<FulfillingCoursesContent
493-
courses={recommendedCourses}
494-
recommendationScope={recommendationScope}
495-
isLoading={isLoading}
496-
onClose={onClose}
497-
/>
468+
<FulfillingCoursesContent />
498469
</div>
499470
</div>
500471
</div>

features/catalog/course-modal-provider.tsx

Lines changed: 37 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,18 @@ import {
44
getSuggestedCoursesForRequirement,
55
} from "./catalog-db";
66
import type { CatalogCourse } from "@/domain/catalog";
7-
import { createContext, useContext, useEffect, useState } from "react";
7+
import {
8+
createContext,
9+
useCallback,
10+
useContext,
11+
useEffect,
12+
useMemo,
13+
useState,
14+
} from "react";
815
import { useAuditContext } from "@/features/audit/audit-provider";
916

1017
// Context for sharing audit data betw sidebar and main
11-
type RecommendationScope = {
18+
export type RecommendationScope = {
1219
requirementTitle?: string;
1320
ruleTitle?: string;
1421
};
@@ -80,35 +87,39 @@ export const CourseModalContextProvider = ({
8087
recommendationScope?.ruleTitle,
8188
]);
8289

83-
const closeModal = () => {
90+
const openModal = useCallback((scope?: RecommendationScope) => {
91+
setRecommendedCourses([]);
92+
setRecommendationScope(scope ?? null);
93+
setIsOpen(true);
94+
}, []);
95+
const closeModal = useCallback(() => {
8496
setIsOpen(false);
8597
setRecommendationScope(null);
86-
};
98+
}, []);
99+
100+
const value = useMemo<CourseModalContextType>(
101+
() => ({
102+
isOpen,
103+
recommendedCourses,
104+
recommendationScope,
105+
isLoadingRecommendedCourses,
106+
openModal,
107+
closeModal,
108+
}),
109+
[
110+
isOpen,
111+
recommendedCourses,
112+
recommendationScope,
113+
isLoadingRecommendedCourses,
114+
openModal,
115+
closeModal,
116+
],
117+
);
87118

88119
return (
89-
<CourseModalContext.Provider
90-
value={{
91-
isOpen,
92-
recommendedCourses,
93-
recommendationScope,
94-
isLoadingRecommendedCourses,
95-
openModal: (scope) => {
96-
setRecommendedCourses([]);
97-
setRecommendationScope(scope ?? null);
98-
setIsOpen(true);
99-
},
100-
closeModal,
101-
}}
102-
>
120+
<CourseModalContext.Provider value={value}>
103121
{children}
104-
105-
<CourseAddModal
106-
isOpen={isOpen}
107-
recommendedCourses={recommendedCourses}
108-
recommendationScope={recommendationScope}
109-
isLoading={isLoadingRecommendedCourses}
110-
onClose={closeModal}
111-
/>
122+
<CourseAddModal />
112123
</CourseModalContext.Provider>
113124
);
114125
};

0 commit comments

Comments
 (0)