From bfbe0f07f27114e00c7229b88a5d1641b601995b Mon Sep 17 00:00:00 2001 From: James Johnson Date: Thu, 22 Feb 2024 12:36:31 -0600 Subject: [PATCH 01/47] starting with naive direct table download as xlsx --- package.json | 2 +- resources/js/components/Table/Table.vue | 17 ++++++++++++++ resources/js/components/Table/index.ts | 8 ++++++- .../components/PersonTable/PersonTable.vue | 23 +++++++++++++++++-- .../stores/useRootCoursePlanningStore.ts | 4 ++-- .../{useTermsStore.ts => useTermStore.ts} | 2 +- resources/js/types/index.ts | 2 +- 7 files changed, 50 insertions(+), 8 deletions(-) rename resources/js/stores/{useTermsStore.ts => useTermStore.ts} (98%) diff --git a/package.json b/package.json index fa08eb9c..a469670f 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "popper.js": "^1.16.1", "v-tooltip": "^2.0.3", "vue": "^3.1.0", - "vue-router": "4" + "xlsx": "^0.18.5" }, "devDependencies": { "@tailwindcss/forms": "^0.5.6", diff --git a/resources/js/components/Table/Table.vue b/resources/js/components/Table/Table.vue index 7a5ab8d6..b01181d3 100644 --- a/resources/js/components/Table/Table.vue +++ b/resources/js/components/Table/Table.vue @@ -4,6 +4,7 @@ class="tw-shadow-sm tw-ring-1 tw-ring-black tw-ring-opacity-5 sm:tw-rounded-lg tw-overflow-auto tw-max-h-[90vh]" > +import { ref } from 'vue'; + withDefaults( defineProps<{ stickyHeader?: boolean; @@ -26,6 +29,20 @@ withDefaults( stickyFirstColumn: false, }, ); + +const tableElement = ref(null); + +export interface Exposed { + getTableElement: () => HTMLTableElement | null; +} + +function getTableElement() { + return tableElement.value; +} + +defineExpose({ + getTableElement, +}); diff --git a/resources/js/icons/DownloadIcon.vue b/resources/js/icons/DownloadIcon.vue new file mode 100644 index 00000000..8de3e17e --- /dev/null +++ b/resources/js/icons/DownloadIcon.vue @@ -0,0 +1,18 @@ + + + diff --git a/resources/js/icons/index.ts b/resources/js/icons/index.ts index 968a9e14..7a0f7f23 100644 --- a/resources/js/icons/index.ts +++ b/resources/js/icons/index.ts @@ -5,6 +5,7 @@ export { default as ChevronRightIcon } from "./ChevronRightIcon.vue"; export { default as CircleCheckIcon } from "./CircleCheckIcon.vue"; export { default as CircleXIcon } from "./CircleXIcon.vue"; export { default as DragHandleIcon } from "./DragHandleIcon.vue"; +export { default as DownloadIcon } from "./DownloadIcon.vue"; export { default as ExternalLinkIcon } from "./ExternalLinkIcon.vue"; export { default as FilterIcon } from "./FilterIcon.vue"; export { default as InfoIcon } from "./InfoIcon.vue"; From 41ca23e4e15c5f2d2e6779ff1c1caf5d9774ea51 Mon Sep 17 00:00:00 2001 From: James Johnson Date: Mon, 26 Feb 2024 12:14:16 -0600 Subject: [PATCH 26/47] use download spreadsheet button in filename, scrub groupname of special chars --- .../CoursePlanningPage/CoursePlanningPage.vue | 53 ++++++++++++++----- .../components/PersonTable/PersonTable.vue | 18 ------- .../PersonTable/usePersonTableData.ts | 2 +- 3 files changed, 42 insertions(+), 31 deletions(-) diff --git a/resources/js/pages/CoursePlanningPage/CoursePlanningPage.vue b/resources/js/pages/CoursePlanningPage/CoursePlanningPage.vue index 56fe8e0a..b5479a42 100644 --- a/resources/js/pages/CoursePlanningPage/CoursePlanningPage.vue +++ b/resources/js/pages/CoursePlanningPage/CoursePlanningPage.vue @@ -44,19 +44,35 @@ > - + + + { + const prettyDate = new Date().toISOString().split("T")[0]; + const groupName = group.value?.group_title + ?.split(" ") + .join("") + .replace(/[^a-zA-Z0-9-]/g, ""); + return `leavePlanningReport_${groupName}_${prettyDate}.xlsx`; +}); diff --git a/resources/js/pages/CoursePlanningPage/components/PersonTable/PersonTable.vue b/resources/js/pages/CoursePlanningPage/components/PersonTable/PersonTable.vue index 7792d630..9ddcf008 100644 --- a/resources/js/pages/CoursePlanningPage/components/PersonTable/PersonTable.vue +++ b/resources/js/pages/CoursePlanningPage/components/PersonTable/PersonTable.vue @@ -1,11 +1,5 @@ diff --git a/resources/js/features/course-planning/helpers/getCourseSpreadsheetRecords.ts b/resources/js/features/course-planning/helpers/getCourseSpreadsheetRecords.ts new file mode 100644 index 00000000..6eed99bc --- /dev/null +++ b/resources/js/features/course-planning/helpers/getCourseSpreadsheetRecords.ts @@ -0,0 +1,37 @@ +import * as T from "@/types"; +import { getTermsWithLeaves } from "./getTermsWithLeaves"; +import { getCourseTableRows } from "./getCourseTableRows"; +import { toCourseSpreadsheetRowRecord } from "./toCourseSpreadsheetRowRecord"; + +export function getCourseSpreadsheetRecords({ + lookups, + filters, +}: { + lookups: T.CoursePlanningLookups; + filters: T.CoursePlanningFilters; +}) { + const termLeaves = getTermsWithLeaves({ + lookups, + filters, + }); + + const leavesRecord: T.CourseSpreadsheetRowRecord = { + id: "leaves", + title: "Leaves", + courseLevel: "", + courseType: "", + ...termLeaves.reduce((acc, { term, leaves }) => { + return { + ...acc, + [term.name]: leaves.map((leave) => leave.type).join(", "), + }; + }, {}), + }; + + const courseRecords: T.CourseSpreadsheetRowRecord[] = getCourseTableRows({ + lookups, + filters, + }).map(toCourseSpreadsheetRowRecord); + + return [leavesRecord, ...courseRecords]; +} diff --git a/resources/js/features/course-planning/helpers/getPersonSpreadsheetRecords.ts b/resources/js/features/course-planning/helpers/getPersonSpreadsheetRecords.ts new file mode 100644 index 00000000..32c66462 --- /dev/null +++ b/resources/js/features/course-planning/helpers/getPersonSpreadsheetRecords.ts @@ -0,0 +1,16 @@ +import * as T from "@/types"; +import { getPersonTableRows } from "./getPersonTableRows"; +import { toPersonSpreadsheetRowRecord } from "./toPersonSpreadsheetRowRecord"; + +export function getPersonSpreadsheetRecords({ + lookups, + filters, +}: { + lookups: T.CoursePlanningLookups; + filters: T.CoursePlanningFilters; +}) { + return getPersonTableRows({ + lookups, + filters, + }).map(toPersonSpreadsheetRowRecord); +} diff --git a/resources/js/features/course-planning/pages/CoursePlanningPage.vue b/resources/js/features/course-planning/pages/CoursePlanningPage.vue index 3a87d585..6191f75e 100644 --- a/resources/js/features/course-planning/pages/CoursePlanningPage.vue +++ b/resources/js/features/course-planning/pages/CoursePlanningPage.vue @@ -50,15 +50,22 @@ :sheetData="[ { sheetName: 'Instructors', - data: coursePlanningStore.instructorSpreadsheetRows, + data: async () => + coursePlanningStore.getPersonSpreadsheetRecordsForRole( + 'PI', + ), }, { sheetName: 'TAs', - data: coursePlanningStore.taSpreadsheetRows, + data: async () => + coursePlanningStore.getPersonSpreadsheetRecordsForRole( + 'TA', + ), }, { sheetName: 'Courses', - data: coursePlanningStore.courseSpreadsheetRows, + data: async () => + coursePlanningStore.getCourseSpreadsheetRecords(), }, ]" /> diff --git a/resources/js/features/course-planning/stores/useCoursePlanningStore.ts b/resources/js/features/course-planning/stores/useCoursePlanningStore.ts index 7ed61bcd..ca967d2c 100644 --- a/resources/js/features/course-planning/stores/useCoursePlanningStore.ts +++ b/resources/js/features/course-planning/stores/useCoursePlanningStore.ts @@ -9,11 +9,8 @@ import { useTermStore } from "@/stores/useTermStore"; import { useLeaveStore } from "./useLeaveStore"; import { countBy, debounce, uniq } from "lodash"; import * as T from "@/types"; -import { getCourseTableRows } from "../helpers/getCourseTableRows"; -import { getPersonTableRows } from "../helpers/getPersonTableRows"; -import { toCourseSpreadsheetRowRecord } from "../helpers/toCourseSpreadsheetRowRecord"; -import { toPersonSpreadsheetRowRecord } from "../helpers/toPersonSpreadsheetRowRecord"; -import { getTermsWithLeaves } from "../helpers/getTermsWithLeaves"; +import { getCourseSpreadsheetRecords } from "../helpers/getCourseSpreadsheetRecords"; +import { getPersonSpreadsheetRecords } from "../helpers/getPersonSpreadsheetRecords"; interface CoursePlanningStoreState { activeGroupId: T.Group["id"] | null; @@ -241,110 +238,6 @@ export const useCoursePlanningStore = defineStore("coursePlanning", () => { getters.isPersonVisible.value(person.emplid), ); }), - coursePlanningLookups: computed( - (): T.CoursePlanningLookups => ({ - courseLookup: stores.courseStore.courseLookup, - termLookup: stores.termsStore.termLookup, - leaveLookup: stores.leaveStore.leaveLookup, - sectionLookup: stores.courseSectionStore.sectionLookup, - enrollmentLookup: stores.enrollmentStore.enrollmentLookup, - personLookup: stores.personStore.personLookupByEmpId, - }), - ), - courseTableRows: computed((): T.CourseTableRow[] => { - if (!state.activeGroupId) { - return []; - } - - return getCourseTableRows({ - lookups: getters.coursePlanningLookups.value, - }); - }), - - personTableRows: computed((): T.PersonTableRow[] => { - if (!state.activeGroupId) { - return []; - } - - return getPersonTableRows({ - lookups: getters.coursePlanningLookups.value, - filters: state.filters, - }); - }), - instructorTableRows: computed((): T.PersonTableRow[] => { - if (!state.activeGroupId) { - return []; - } - - const lookups = {}; - - return getPersonTableRows({ - lookups: getters.coursePlanningLookups.value, - filters: { - ...state.filters, - includedEnrollmentRoles: new Set(["PI"]), - }, - }); - }), - taTableRows: computed((): T.PersonTableRow[] => { - if (!state.activeGroupId) { - return []; - } - - return getPersonTableRows({ - lookups: getters.coursePlanningLookups.value, - filters: { - ...state.filters, - includedEnrollmentRoles: new Set(["TA"]), - }, - }); - }), - instructorSpreadsheetRows: computed((): T.PersonSpreadsheetRowRecord[] => { - if (!state.activeGroupId) { - return []; - } - - return getters.instructorTableRows.value.map( - toPersonSpreadsheetRowRecord, - ); - }), - - taSpreadsheetRows: computed((): T.PersonSpreadsheetRowRecord[] => { - if (!state.activeGroupId) { - return []; - } - - return getters.taTableRows.value.map(toPersonSpreadsheetRowRecord); - }), - - courseSpreadsheetRows: computed((): T.CourseSpreadsheetRowRecord[] => { - if (!state.activeGroupId) { - return []; - } - - const termLeaves = getTermsWithLeaves({ - lookups: getters.coursePlanningLookups.value, - filters: state.filters, - }); - - const leavesRecord: T.CourseSpreadsheetRowRecord = { - id: "leaves", - title: "Leaves", - courseLevel: "", - courseType: "", - ...termLeaves.reduce((acc, { term, leaves }) => { - return { - ...acc, - [term.name]: leaves.map((leave) => leave.type).join(", "), - }; - }, {}), - }; - - const courseRecords: T.CourseSpreadsheetRowRecord[] = - getters.courseTableRows.value.map(toCourseSpreadsheetRowRecord); - - return [leavesRecord, ...courseRecords]; - }), }; const actions = { @@ -709,6 +602,46 @@ export const useCoursePlanningStore = defineStore("coursePlanning", () => { return getters.isPersonVisible.value(person.emplid); }, + + getCoursePlanningLookups(): T.CoursePlanningLookups { + return { + personLookup: stores.personStore.personLookupByEmpId, + courseLookup: stores.courseStore.courseLookup, + sectionLookup: stores.courseSectionStore.sectionLookup, + enrollmentLookup: stores.enrollmentStore.enrollmentLookup, + termLookup: stores.termsStore.termLookup, + leaveLookup: stores.leaveStore.leaveLookup, + }; + }, + + getCoursePlanningFilters(): T.CoursePlanningFilters { + return state.filters; + }, + + getCourseSpreadsheetRecords(): T.CourseSpreadsheetRowRecord[] { + const lookups = methods.getCoursePlanningLookups(); + const filters = methods.getCoursePlanningFilters(); + + return getCourseSpreadsheetRecords({ + lookups, + filters, + }); + }, + + getPersonSpreadsheetRecordsForRole( + role: T.EnrollmentRole, + ): T.PersonSpreadsheetRowRecord[] { + const lookups = methods.getCoursePlanningLookups(); + const filters = methods.getCoursePlanningFilters(); + + return getPersonSpreadsheetRecords({ + lookups, + filters: { + ...filters, + includedEnrollmentRoles: new Set([role]), + }, + }); + }, }; return { From 0187069c842d9f396b4c6adab6cf3baf30387c8b Mon Sep 17 00:00:00 2001 From: James Johnson Date: Wed, 28 Feb 2024 20:00:51 -0600 Subject: [PATCH 34/47] include person name and emplid with leave info on course tab --- .../components/PersonTable/PersonTableCell.vue | 2 +- .../helpers/getCourseSpreadsheetRecords.ts | 14 +++++++++++--- .../helpers/getJoinedEnrollmentRecord.ts | 2 +- .../course-planning/helpers/getLeavesInTerm.ts | 16 +++++++++++++--- .../helpers/getPersonTableRows.ts | 4 +++- .../stores/useCoursePlanningStore.ts | 3 ++- .../course-planning/stores/useLeaveStore.ts | 2 +- .../course-planning/stores/usePersonStore.ts | 11 ++++++++--- resources/js/types/index.ts | 12 ++++++++---- 9 files changed, 48 insertions(+), 18 deletions(-) diff --git a/resources/js/features/course-planning/components/PersonTable/PersonTableCell.vue b/resources/js/features/course-planning/components/PersonTable/PersonTableCell.vue index 1239353e..f8e51f49 100644 --- a/resources/js/features/course-planning/components/PersonTable/PersonTableCell.vue +++ b/resources/js/features/course-planning/components/PersonTable/PersonTableCell.vue @@ -89,7 +89,7 @@ const isShowingEditModal = ref(false); const termLeavesForPerson = computed(() => coursePlanningStore.leaveStore - .getLeavesByPersonId(props.person.id) + .getLeavesByUserId(props.person.id) .filter((leave) => leave.termIds?.includes(props.term.id)), ); diff --git a/resources/js/features/course-planning/helpers/getCourseSpreadsheetRecords.ts b/resources/js/features/course-planning/helpers/getCourseSpreadsheetRecords.ts index 6eed99bc..a83169b5 100644 --- a/resources/js/features/course-planning/helpers/getCourseSpreadsheetRecords.ts +++ b/resources/js/features/course-planning/helpers/getCourseSpreadsheetRecords.ts @@ -2,6 +2,7 @@ import * as T from "@/types"; import { getTermsWithLeaves } from "./getTermsWithLeaves"; import { getCourseTableRows } from "./getCourseTableRows"; import { toCourseSpreadsheetRowRecord } from "./toCourseSpreadsheetRowRecord"; +import { capitalize } from "lodash"; export function getCourseSpreadsheetRecords({ lookups, @@ -18,12 +19,19 @@ export function getCourseSpreadsheetRecords({ const leavesRecord: T.CourseSpreadsheetRowRecord = { id: "leaves", title: "Leaves", - courseLevel: "", - courseType: "", + courseLevel: "-", + courseType: "-", ...termLeaves.reduce((acc, { term, leaves }) => { return { ...acc, - [term.name]: leaves.map((leave) => leave.type).join(", "), + [term.name]: leaves + .map( + (leave) => + `${leave.person.displayName} (${ + leave.person.emplid + }) - ${capitalize(leave.type)} Leave`, + ) + .join(", "), }; }, {}), }; diff --git a/resources/js/features/course-planning/helpers/getJoinedEnrollmentRecord.ts b/resources/js/features/course-planning/helpers/getJoinedEnrollmentRecord.ts index 36d77ff3..ca90a0c3 100644 --- a/resources/js/features/course-planning/helpers/getJoinedEnrollmentRecord.ts +++ b/resources/js/features/course-planning/helpers/getJoinedEnrollmentRecord.ts @@ -10,7 +10,7 @@ export function getJoinedEnrollmentRecord({ const section = lookups.sectionLookup[enrollment.sectionId]; const course = lookups.courseLookup[section.courseId]; const term = lookups.termLookup[section.termId]; - const person = lookups.personLookup[enrollment.emplid] ?? null; + const person = lookups.personLookupByEmplid[enrollment.emplid] ?? null; if (!section || !course || !term || !person) { throw new Error("Missing data for enrollment record"); diff --git a/resources/js/features/course-planning/helpers/getLeavesInTerm.ts b/resources/js/features/course-planning/helpers/getLeavesInTerm.ts index 0220b1bf..98a6bfd4 100644 --- a/resources/js/features/course-planning/helpers/getLeavesInTerm.ts +++ b/resources/js/features/course-planning/helpers/getLeavesInTerm.ts @@ -4,10 +4,20 @@ export function getLeavesInTerm({ lookups, term, }: { - lookups: Pick; + lookups: Pick< + T.CoursePlanningLookups, + "leaveLookup" | "personLookupByUserId" + >; term: T.Term; -}) { - return Object.values(lookups.leaveLookup).filter( +}): T.LeaveWithPerson[] { + const leaves = Object.values(lookups.leaveLookup).filter( (leave) => leave.termIds?.includes(term.id), ); + + const leavesWithPerson: T.LeaveWithPerson[] = leaves.map((leave) => { + const person = lookups.personLookupByUserId[leave.user_id]; + return { ...leave, person }; + }); + + return leavesWithPerson; } diff --git a/resources/js/features/course-planning/helpers/getPersonTableRows.ts b/resources/js/features/course-planning/helpers/getPersonTableRows.ts index 1f11950d..ddf0b463 100644 --- a/resources/js/features/course-planning/helpers/getPersonTableRows.ts +++ b/resources/js/features/course-planning/helpers/getPersonTableRows.ts @@ -18,7 +18,9 @@ export function getPersonTableRows({ lookups: T.CoursePlanningLookups; filters: T.CoursePlanningFilters; }): T.PersonTableRow[] { - const sortedPeople = Object.values(lookups.personLookup).sort(sortByName); + const sortedPeople = Object.values(lookups.personLookupByEmplid).sort( + sortByName, + ); const sortedTerms = Object.values(lookups.termLookup).sort( (a, b) => a.id - b.id, ); diff --git a/resources/js/features/course-planning/stores/useCoursePlanningStore.ts b/resources/js/features/course-planning/stores/useCoursePlanningStore.ts index ca967d2c..998ce59c 100644 --- a/resources/js/features/course-planning/stores/useCoursePlanningStore.ts +++ b/resources/js/features/course-planning/stores/useCoursePlanningStore.ts @@ -605,7 +605,8 @@ export const useCoursePlanningStore = defineStore("coursePlanning", () => { getCoursePlanningLookups(): T.CoursePlanningLookups { return { - personLookup: stores.personStore.personLookupByEmpId, + personLookupByEmplid: stores.personStore.personLookupByEmpId, + personLookupByUserId: stores.personStore.personLookupByUserId, courseLookup: stores.courseStore.courseLookup, sectionLookup: stores.courseSectionStore.sectionLookup, enrollmentLookup: stores.enrollmentStore.enrollmentLookup, diff --git a/resources/js/features/course-planning/stores/useLeaveStore.ts b/resources/js/features/course-planning/stores/useLeaveStore.ts index 2598b13a..70b9e993 100644 --- a/resources/js/features/course-planning/stores/useLeaveStore.ts +++ b/resources/js/features/course-planning/stores/useLeaveStore.ts @@ -17,7 +17,7 @@ export const useLeaveStore = defineStore("leave", () => { const getters = { leaves: computed((): T.Leave[] => Object.values(state.leaveLookup)), - getLeavesByPersonId: computed(() => { + getLeavesByUserId: computed(() => { const lookupByUserId = groupBy(getters.leaves.value, "user_id"); return (personId: T.Person["id"]): T.Leave[] => diff --git a/resources/js/features/course-planning/stores/usePersonStore.ts b/resources/js/features/course-planning/stores/usePersonStore.ts index 8b7dd2c6..b0124240 100644 --- a/resources/js/features/course-planning/stores/usePersonStore.ts +++ b/resources/js/features/course-planning/stores/usePersonStore.ts @@ -30,15 +30,20 @@ export const usePersonStore = defineStore("person", () => { return state.personLookupByEmpId[emplId] ?? null; }, ), - getPersonByUserId: computed(() => { + personLookupByUserId: computed((): Record => { const lookupByUserId: Record = keyBy( getters.allPeople.value, "id", ); - return (userId: T.Person["id"]): T.Person | null => - lookupByUserId[userId] ?? null; + return lookupByUserId; }), + + getPersonByUserId: computed( + () => + (userId: T.Person["id"]): T.Person | null => + getters.personLookupByUserId[userId] ?? null, + ), getPeopleWithRoles: computed( () => (roles: T.Enrollment["role"][]): T.Person[] => { diff --git a/resources/js/types/index.ts b/resources/js/types/index.ts index 6298e1ea..89ce5cf2 100644 --- a/resources/js/types/index.ts +++ b/resources/js/types/index.ts @@ -184,7 +184,7 @@ export type LeaveStatus = (typeof leaveStatuses)[keyof typeof leaveStatuses]; export interface Leave { id: number; - user_id: number; + user_id: User["id"]; description: string; type: LeaveType; status: LeaveStatus; @@ -197,6 +197,9 @@ export interface Leave { deleted_at?: ISODateTime | null; } +export interface LeaveWithPerson extends Leave { + person: Person; +} export interface NewLeave { id?: string | number; user_id: number; @@ -225,7 +228,7 @@ export const enrollmentRoleMap = { export type EnrollmentRole = keyof typeof enrollmentRoleMap; export interface Person { - id: number; + id: User["id"]; emplid: number; title: string; jobCode: string; @@ -383,7 +386,8 @@ export interface JoinedEnrollmentRecord { } export interface CoursePlanningLookups { - personLookup: Record; + personLookupByEmplid: Record; + personLookupByUserId: Record; termLookup: Record; courseLookup: Record; sectionLookup: Record; @@ -409,7 +413,7 @@ export interface PersonSpreadsheetRowRecord { export interface TermLeaves { term: Term; - leaves: Leave[]; + leaves: LeaveWithPerson[]; } export type LeaveRow = ["leaves", ...TermLeaves[]]; From 240636f95359c439344645ca7a9616d199056df8 Mon Sep 17 00:00:00 2001 From: James Johnson Date: Wed, 28 Feb 2024 20:06:38 -0600 Subject: [PATCH 35/47] cleanup --- package.json | 2 +- .../CourseTable/useCourseTableData.test.ts | 172 ----------- .../usePersonTableData.test.ts.snap | 286 ------------------ .../PersonTable/usePersonTableData.test.ts | 237 --------------- 4 files changed, 1 insertion(+), 696 deletions(-) delete mode 100644 resources/js/features/course-planning/components/CourseTable/useCourseTableData.test.ts delete mode 100644 resources/js/features/course-planning/components/PersonTable/__snapshots__/usePersonTableData.test.ts.snap delete mode 100644 resources/js/features/course-planning/components/PersonTable/usePersonTableData.test.ts diff --git a/package.json b/package.json index e7f33f25..ac50b3f8 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "lint": "eslint --ext .js,.vue resources/js", "lint:watch": "nodemon --exec 'npm run lint' --ext js,vue", "test": "npm run test:unit && npm run test:e2e", - "test:unit": "jest", + "test:unit": "jest --passWithNoTests", "test:e2e": "npm run cypress:headless" }, "dependencies": { diff --git a/resources/js/features/course-planning/components/CourseTable/useCourseTableData.test.ts b/resources/js/features/course-planning/components/CourseTable/useCourseTableData.test.ts deleted file mode 100644 index abf0da26..00000000 --- a/resources/js/features/course-planning/components/CourseTable/useCourseTableData.test.ts +++ /dev/null @@ -1,172 +0,0 @@ -// import * as T from "@/types"; -// import { -// getLeavesInTerm, -// getEnrollmentsForCourseInTerm, -// getLeavesRow, -// getCourseRows, -// } from "./useCourseTableData"; -// import { createMockLookups } from "../../stores/createMockLookups"; - -// describe("getCourseTableRows", () => { -// it("get leaves in a given term", () => { -// const lookups = createMockLookups(); -// const term = lookups.termLookup[1]; -// const leaves = getLeavesInTerm({ -// leaveLookup: lookups.leaveLookup, -// term, -// }); -// expect(leaves).toMatchInlineSnapshot(` -// [ -// { -// "created_at": "2021-08-01", -// "description": "Sabbatical", -// "end_date": "2021-12-31", -// "id": 1, -// "start_date": "2021-08-01", -// "status": "confirmed", -// "termIds": [ -// 1, -// ], -// "type": "sabbatical", -// "updated_at": "2021-08-01", -// "user_id": 210, -// }, -// ] -// `); -// }); - -// it("gets a list of joined enrollment records for a course in a given term", () => { -// const lookups = createMockLookups(); - -// const courseId = "AFRO-1009"; -// const termId = 1; // Fall 2021 -// const course = lookups.courseLookup[courseId]; -// const term = lookups.termLookup[termId]; - -// const joinedEnrollments: T.JoinedEnrollmentRecord[] = -// getEnrollmentsForCourseInTerm({ -// course, -// term, -// ...lookups, -// }); - -// expect(joinedEnrollments.length).toBe(2); -// expect(joinedEnrollments[0].course.id).toBe(courseId); -// expect(joinedEnrollments[0].term.id).toBe(termId); -// expect(joinedEnrollments[0].person.emplid).toBe(12345); -// expect(joinedEnrollments[0].section.id).toBe("sis-39"); -// expect(joinedEnrollments[0].enrollment.id).toBe("sis-sis-39-12345"); -// }); - -// it("should get a leave row", () => { -// const lookups = createMockLookups(); -// const [leaveRowLabel, ...termLeaves] = getLeavesRow({ lookups }); -// expect(leaveRowLabel).toBe("leaves"); -// expect(termLeaves.length).toBe(2); -// expect(termLeaves[0].term.id).toBe(1); -// expect(termLeaves[0]).toMatchInlineSnapshot(` -// { -// "leaves": [ -// { -// "created_at": "2021-08-01", -// "description": "Sabbatical", -// "end_date": "2021-12-31", -// "id": 1, -// "start_date": "2021-08-01", -// "status": "confirmed", -// "termIds": [ -// 1, -// ], -// "type": "sabbatical", -// "updated_at": "2021-08-01", -// "user_id": 210, -// }, -// ], -// "term": { -// "endDate": "2021-12-31", -// "id": 1, -// "name": "Fall 2021", -// "startDate": "2021-08-01", -// }, -// } -// `); -// }); - -// it("should get the course rows", () => { -// const lookups = createMockLookups(); -// const courseRows = getCourseRows({ lookups }); -// expect(courseRows.length).toBe(2); -// const [course, ...termsWithJoinedEnrollments] = courseRows[0]; -// expect(course.id).toBe("AFRO-1009"); -// expect(termsWithJoinedEnrollments.length).toBe(2); -// const { term, joinedEnrollments } = termsWithJoinedEnrollments[0]; -// expect(term.id).toBe(1); -// expect(joinedEnrollments.length).toBe(2); -// expect(joinedEnrollments[0].course).toMatchInlineSnapshot(` -// { -// "catalogNumber": "1009", -// "courseCode": "AFRO-1009", -// "courseLevel": "UGRD", -// "courseType": "LEC", -// "id": "AFRO-1009", -// "source": "sis", -// "subject": "AFRO", -// "title": "History of Women in Africa", -// } -// `); -// expect(joinedEnrollments[0].term).toMatchInlineSnapshot(` -// { -// "endDate": "2021-12-31", -// "id": 1, -// "name": "Fall 2021", -// "startDate": "2021-08-01", -// } -// `); -// expect(joinedEnrollments[0].person).toMatchInlineSnapshot(` -// { -// "academicAppointment": "Faculty", -// "displayName": "Dade Murphy", -// "email": "dmurphy1234@umn.edu", -// "emplid": 12345, -// "givenName": "Dade", -// "id": 210, -// "jobCode": "9402", -// "leaveIds": [ -// 1, -// 2, -// ], -// "midcareerEligible": false, -// "sslApplyEligible": true, -// "sslEligible": true, -// "surName": "Murphy", -// "title": "Associate Professor", -// } -// `); -// expect(joinedEnrollments[0].section).toMatchInlineSnapshot(` -// { -// "classNumber": 39, -// "classSection": "001", -// "courseId": "AFRO-1009", -// "dbId": null, -// "enrollmentCap": 18, -// "enrollmentTotal": 16, -// "groupId": 1, -// "id": "sis-39", -// "isCancelled": false, -// "isPublished": true, -// "termId": 1, -// "waitlistCap": 0, -// "waitlistTotal": 0, -// } -// `); -// expect(joinedEnrollments[0].enrollment).toMatchInlineSnapshot(` -// { -// "dbId": null, -// "emplid": 12345, -// "id": "sis-sis-39-12345", -// "role": "PI", -// "sectionId": "sis-39", -// } -// `); -// }); -// }); diff --git a/resources/js/features/course-planning/components/PersonTable/__snapshots__/usePersonTableData.test.ts.snap b/resources/js/features/course-planning/components/PersonTable/__snapshots__/usePersonTableData.test.ts.snap deleted file mode 100644 index 14256b92..00000000 --- a/resources/js/features/course-planning/components/PersonTable/__snapshots__/usePersonTableData.test.ts.snap +++ /dev/null @@ -1,286 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`usePersonTableData gets person table rows 1`] = ` -[ - [ - { - "academicAppointment": "Faculty", - "displayName": "Kate Libby", - "email": "klibby@umn.edu", - "emplid": 12346, - "givenName": "Kate", - "id": 211, - "jobCode": "9401", - "leaveIds": [], - "midcareerEligible": false, - "sslApplyEligible": true, - "sslEligible": true, - "surName": "Libby", - "title": "Assistant Professor", - }, - { - "enrollments": [ - { - "course": { - "catalogNumber": "1009", - "courseCode": "AFRO-1009", - "courseLevel": "UGRD", - "courseType": "LEC", - "id": "AFRO-1009", - "source": "sis", - "subject": "AFRO", - "title": "History of Women in Africa", - }, - "enrollment": { - "dbId": null, - "emplid": 12346, - "id": "sis-sis-39-12346", - "role": "PI", - "sectionId": "sis-39", - }, - "person": { - "academicAppointment": "Faculty", - "displayName": "Kate Libby", - "email": "klibby@umn.edu", - "emplid": 12346, - "givenName": "Kate", - "id": 211, - "jobCode": "9401", - "leaveIds": [], - "midcareerEligible": false, - "sslApplyEligible": true, - "sslEligible": true, - "surName": "Libby", - "title": "Assistant Professor", - }, - "section": { - "classNumber": 39, - "classSection": "001", - "courseId": "AFRO-1009", - "dbId": null, - "enrollmentCap": 18, - "enrollmentTotal": 16, - "groupId": 1, - "id": "sis-39", - "isCancelled": false, - "isPublished": true, - "termId": 1, - "waitlistCap": 0, - "waitlistTotal": 0, - }, - "term": { - "endDate": "2021-12-31", - "id": 1, - "name": "Fall 2021", - "startDate": "2021-08-01", - }, - }, - ], - "leaves": [], - "term": { - "endDate": "2021-12-31", - "id": 1, - "name": "Fall 2021", - "startDate": "2021-08-01", - }, - }, - { - "enrollments": [], - "leaves": [], - "term": { - "endDate": "2022-05-31", - "id": 2, - "name": "Spring 2022", - "startDate": "2022-01-01", - }, - }, - ], - [ - { - "academicAppointment": "Faculty", - "displayName": "Dade Murphy", - "email": "dmurphy1234@umn.edu", - "emplid": 12345, - "givenName": "Dade", - "id": 210, - "jobCode": "9402", - "leaveIds": [ - 1, - 2, - ], - "midcareerEligible": false, - "sslApplyEligible": true, - "sslEligible": true, - "surName": "Murphy", - "title": "Associate Professor", - }, - { - "enrollments": [ - { - "course": { - "catalogNumber": "1009", - "courseCode": "AFRO-1009", - "courseLevel": "UGRD", - "courseType": "LEC", - "id": "AFRO-1009", - "source": "sis", - "subject": "AFRO", - "title": "History of Women in Africa", - }, - "enrollment": { - "dbId": null, - "emplid": 12345, - "id": "sis-sis-39-12345", - "role": "PI", - "sectionId": "sis-39", - }, - "person": { - "academicAppointment": "Faculty", - "displayName": "Dade Murphy", - "email": "dmurphy1234@umn.edu", - "emplid": 12345, - "givenName": "Dade", - "id": 210, - "jobCode": "9402", - "leaveIds": [ - 1, - 2, - ], - "midcareerEligible": false, - "sslApplyEligible": true, - "sslEligible": true, - "surName": "Murphy", - "title": "Associate Professor", - }, - "section": { - "classNumber": 39, - "classSection": "001", - "courseId": "AFRO-1009", - "dbId": null, - "enrollmentCap": 18, - "enrollmentTotal": 16, - "groupId": 1, - "id": "sis-39", - "isCancelled": false, - "isPublished": true, - "termId": 1, - "waitlistCap": 0, - "waitlistTotal": 0, - }, - "term": { - "endDate": "2021-12-31", - "id": 1, - "name": "Fall 2021", - "startDate": "2021-08-01", - }, - }, - { - "course": { - "catalogNumber": "1011", - "courseCode": "AFRO-1011", - "courseLevel": "UGRD", - "courseType": "LEC", - "id": "AFRO-1011", - "source": "sis", - "subject": "AFRO", - "title": "Advanced Topics in African History", - }, - "enrollment": { - "dbId": null, - "emplid": 12345, - "id": "sis-sis-40-12345", - "role": "PI", - "sectionId": "sis-40", - }, - "person": { - "academicAppointment": "Faculty", - "displayName": "Dade Murphy", - "email": "dmurphy1234@umn.edu", - "emplid": 12345, - "givenName": "Dade", - "id": 210, - "jobCode": "9402", - "leaveIds": [ - 1, - 2, - ], - "midcareerEligible": false, - "sslApplyEligible": true, - "sslEligible": true, - "surName": "Murphy", - "title": "Associate Professor", - }, - "section": { - "classNumber": 40, - "classSection": "001", - "courseId": "AFRO-1011", - "dbId": null, - "enrollmentCap": 18, - "enrollmentTotal": 16, - "groupId": 1, - "id": "sis-40", - "isCancelled": false, - "isPublished": true, - "termId": 1, - "waitlistCap": 0, - "waitlistTotal": 0, - }, - "term": { - "endDate": "2021-12-31", - "id": 1, - "name": "Fall 2021", - "startDate": "2021-08-01", - }, - }, - ], - "leaves": [ - { - "created_at": "2021-08-01", - "description": "Sabbatical", - "end_date": "2021-12-31", - "id": 1, - "start_date": "2021-08-01", - "status": "confirmed", - "termIds": [ - 1, - ], - "type": "sabbatical", - "updated_at": "2021-08-01", - "user_id": 210, - }, - ], - "term": { - "endDate": "2021-12-31", - "id": 1, - "name": "Fall 2021", - "startDate": "2021-08-01", - }, - }, - { - "enrollments": [], - "leaves": [ - { - "created_at": "2022-01-01", - "description": "Sabbatical", - "end_date": "2022-05-31", - "id": 2, - "start_date": "2022-01-01", - "status": "confirmed", - "termIds": [ - 2, - ], - "type": "sabbatical", - "updated_at": "2022-01-01", - "user_id": 210, - }, - ], - "term": { - "endDate": "2022-05-31", - "id": 2, - "name": "Spring 2022", - "startDate": "2022-01-01", - }, - }, - ], -] -`; diff --git a/resources/js/features/course-planning/components/PersonTable/usePersonTableData.test.ts b/resources/js/features/course-planning/components/PersonTable/usePersonTableData.test.ts deleted file mode 100644 index b9f43c5e..00000000 --- a/resources/js/features/course-planning/components/PersonTable/usePersonTableData.test.ts +++ /dev/null @@ -1,237 +0,0 @@ -// import * as T from "@/types"; -// import { getTableRows, toSpreadsheetRow } from "./usePersonTableData"; -// import { createMockLookups } from "../../stores/createMockLookups"; -// import { keyBy } from "lodash"; - -// describe("usePersonTableData", () => { -// it("gets person table rows", () => { -// const lookups = createMockLookups(); -// const personTableData = getTableRows(lookups); -// expect(personTableData).toMatchSnapshot(); -// }); - -// it("converts data to spreadsheet json format", () => { -// const rows = getTableRows(createMockLookups()); - -// expect(toSpreadsheetRow(rows[0])).toMatchInlineSnapshot(` -// { -// "Fall 2021": "AFRO-1009", -// "Spring 2022": "", -// "academicAppointment": "Faculty", -// "givenName": "Kate", -// "id": 12346, -// "surName": "Libby", -// } -// `); -// }); - -// it("filters for course level", () => { -// const lookups = createMockLookups(); -// // change the course level of one of the courses -// lookups.courseLookup["AFRO-1009"].courseLevel = "GRD"; - -// const originalRows = getTableRows(lookups); - -// // expect that we can find some rows with AFRO-1009 -// expect(toSpreadsheetRow(originalRows[0])).toMatchInlineSnapshot(` -// { -// "Fall 2021": "AFRO-1009", -// "Spring 2022": "", -// "academicAppointment": "Faculty", -// "givenName": "Kate", -// "id": 12346, -// "surName": "Libby", -// } -// `); - -// const filters = { -// excludedCourseLevels: new Set(["GRD"]), -// }; - -// const rows = getTableRows({ -// ...lookups, -// filters, -// }); - -// // expect that AFRO-1009 is not in any spreadsheet row -// expect(JSON.stringify(rows)).not.toContain("AFRO-1009"); -// }); - -// it("filters for course type", () => { -// const lookups = createMockLookups(); -// lookups.courseLookup["AFRO-1009"].courseType = "DIS"; - -// const originalRows = getTableRows(lookups); -// expect(toSpreadsheetRow(originalRows[0])).toMatchInlineSnapshot(` -// { -// "Fall 2021": "AFRO-1009", -// "Spring 2022": "", -// "academicAppointment": "Faculty", -// "givenName": "Kate", -// "id": 12346, -// "surName": "Libby", -// } -// `); - -// const filters = { -// excludedCourseTypes: new Set(["DIS"]), -// }; - -// const rows = getTableRows({ -// ...lookups, -// filters, -// }); - -// expect(JSON.stringify(rows)).not.toContain("AFRO-1009"); -// }); - -// it("filters for Employee Appointment", () => { -// const lookups = createMockLookups(); -// lookups.personLookup[12346].academicAppointment = "Staff"; - -// const originalRows = getTableRows(lookups); -// expect(toSpreadsheetRow(originalRows[0])).toMatchInlineSnapshot(` -// { -// "Fall 2021": "AFRO-1009", -// "Spring 2022": "", -// "academicAppointment": "Staff", -// "givenName": "Kate", -// "id": 12346, -// "surName": "Libby", -// } -// `); - -// const filters = { -// excludedAcadAppts: new Set(["Staff"]), -// }; - -// const rows = getTableRows({ -// ...lookups, -// filters, -// }); - -// const firstEntry = toSpreadsheetRow(rows[0]); - -// expect(firstEntry.givenName).not.toBe("Kate"); -// }); - -// it("filters by term range", () => { -// const lookups = createMockLookups(); -// const originalRows = getTableRows(lookups); -// expect(toSpreadsheetRow(originalRows[0])).toMatchInlineSnapshot(` -// { -// "Fall 2021": "AFRO-1009", -// "Spring 2022": "", -// "academicAppointment": "Faculty", -// "givenName": "Kate", -// "id": 12346, -// "surName": "Libby", -// } -// `); - -// const filters = { -// // only Fall 2021 -// startTermId: null, -// endTermId: 1, -// }; - -// const rows = getTableRows({ -// ...lookups, -// filters, -// }); - -// expect(toSpreadsheetRow(rows[0])).toMatchInlineSnapshot(` -// { -// "Fall 2021": "AFRO-1009", -// "academicAppointment": "Faculty", -// "givenName": "Kate", -// "id": 12346, -// "surName": "Libby", -// } -// `); -// }); - -// it('excludes draft courses unless "Planning Mode" is enabled', () => { -// const lookups = createMockLookups(); - -// // add a draft section -// const plannedSection: T.CourseSection = { -// ...lookups.sectionLookup["AFRO-1011"], -// id: "db-555", -// dbId: 555, -// classNumber: null, -// courseId: "AFRO-1011", -// termId: 2, -// isPublished: false, -// }; - -// const enrollmentInPlannedSection: T.Enrollment = { -// id: "db-444", -// dbId: 444, -// sectionId: "db-555", -// emplid: 12346, -// role: "PI", -// }; - -// lookups.sectionLookup["db-555"] = plannedSection; -// lookups.enrollmentLookup["db-444"] = enrollmentInPlannedSection; - -// const rows = getTableRows(lookups); -// expect(toSpreadsheetRow(rows[0])).toMatchInlineSnapshot(` -// { -// "Fall 2021": "AFRO-1009", -// "Spring 2022": "", -// "academicAppointment": "Faculty", -// "givenName": "Kate", -// "id": 12346, -// "surName": "Libby", -// } -// `); -// }); - -// it("excludes people with no enrollments", () => { -// const lookups = createMockLookups(); -// const rows = getTableRows(lookups); - -// expect(rows.length).toBe(2); - -// const personToExclude = lookups.personLookup[12345]; - -// // remove all enrollments for '12345' -// const excludedEnrollments = Object.values(lookups.enrollmentLookup).filter( -// (enrollment) => enrollment.emplid !== personToExclude.emplid, -// ); -// lookups.enrollmentLookup = keyBy(excludedEnrollments, "id"); - -// const updatedRows = getTableRows(lookups); -// expect(updatedRows.length).toBe(1); -// const firstEntry = toSpreadsheetRow(updatedRows[0]); -// expect(firstEntry.id).not.toBe(12345); -// }); - -// it("filters by enrolled role", () => { -// const lookups = createMockLookups(); -// const rows = getTableRows(lookups); - -// // change the role of one of the enrollments -// const firstEnrollment = Object.values(lookups.enrollmentLookup)[0]; -// firstEnrollment.role = "TA"; - -// // before we set the filter, expect that this enrollment is in the table -// expect(JSON.stringify(rows)).toContain("TA"); -// expect(JSON.stringify(rows)).toContain(firstEnrollment.id); - -// // now set the filter to exclude TAs -// const filters = { -// excludedEnrollmentRoles: new Set(["TA"]), -// }; - -// const updatedRows = getTableRows({ -// ...lookups, -// filters, -// }); - -// expect(JSON.stringify(updatedRows)).not.toContain("TA"); -// expect(JSON.stringify(updatedRows)).not.toContain(firstEnrollment.id); -// }); -// }); From 8ba7d7768a7645cffe97fee609dc1108d5c382e9 Mon Sep 17 00:00:00 2001 From: James Johnson Date: Wed, 28 Feb 2024 20:08:08 -0600 Subject: [PATCH 36/47] cleanup --- .../stores/createMockLookups.ts | 167 ------------------ 1 file changed, 167 deletions(-) delete mode 100644 resources/js/features/course-planning/stores/createMockLookups.ts diff --git a/resources/js/features/course-planning/stores/createMockLookups.ts b/resources/js/features/course-planning/stores/createMockLookups.ts deleted file mode 100644 index bba35123..00000000 --- a/resources/js/features/course-planning/stores/createMockLookups.ts +++ /dev/null @@ -1,167 +0,0 @@ -import * as T from "@/types"; -import { cloneDeep } from "lodash"; - -const termLookup: Record = { - 1: { - id: 1, - name: "Fall 2021", - startDate: "2021-08-01", - endDate: "2021-12-31", - }, - 2: { - id: 2, - name: "Spring 2022", - startDate: "2022-01-01", - endDate: "2022-05-31", - }, -}; - -const personLookup: Record = { - 12345: { - id: 210, - givenName: "Dade", - surName: "Murphy", - displayName: "Dade Murphy", - email: "dmurphy1234@umn.edu", - title: "Associate Professor", - jobCode: "9402", - leaveIds: [1, 2], - academicAppointment: "Faculty", - emplid: 12345, - sslEligible: true, - midcareerEligible: false, - sslApplyEligible: true, - }, - 12346: { - id: 211, - givenName: "Kate", - surName: "Libby", - displayName: "Kate Libby", - email: "klibby@umn.edu", - title: "Assistant Professor", - jobCode: "9401", - leaveIds: [], - academicAppointment: "Faculty", - emplid: 12346, - sslEligible: true, - midcareerEligible: false, - sslApplyEligible: true, - }, -}; - -const courseLookup: Record = { - "AFRO-1009": { - id: "AFRO-1009", - subject: "AFRO", - catalogNumber: "1009", - title: "History of Women in Africa", - courseType: "LEC", - courseLevel: "UGRD", - courseCode: "AFRO-1009", - source: "sis", - }, - "AFRO-1011": { - id: "AFRO-1011", - subject: "AFRO", - catalogNumber: "1011", - title: "Advanced Topics in African History", - courseType: "LEC", - courseLevel: "UGRD", - courseCode: "AFRO-1011", - source: "sis", - }, -}; - -const sectionLookup: Record = { - "sis-39": { - id: "sis-39", - classNumber: 39, - dbId: null, - courseId: "AFRO-1009", - termId: 1, - groupId: 1, - classSection: "001", - enrollmentCap: 18, - enrollmentTotal: 16, - waitlistCap: 0, - waitlistTotal: 0, - isPublished: true, - isCancelled: false, - }, - "sis-40": { - id: "sis-40", - classNumber: 40, - dbId: null, - courseId: "AFRO-1011", - termId: 1, - groupId: 1, - classSection: "001", - enrollmentCap: 18, - enrollmentTotal: 16, - waitlistCap: 0, - waitlistTotal: 0, - isPublished: true, - isCancelled: false, - }, -}; - -const enrollmentLookup: Record = { - "sis-sis-39-12345": { - id: "sis-sis-39-12345", - dbId: null, - emplid: 12345, - sectionId: "sis-39", - role: "PI", - }, - "sis-sis-40-12345": { - id: "sis-sis-40-12345", - dbId: null, - emplid: 12345, - sectionId: "sis-40", - role: "PI", - }, - "sis-sis-39-12346": { - id: "sis-sis-39-12346", - dbId: null, - emplid: 12346, - sectionId: "sis-39", - role: "PI", - }, -}; - -const leaveLookup: Record = { - 1: { - id: 1, - user_id: 210, - termIds: [1], - type: T.leaveTypes.SABBATICAL, - status: T.leaveStatuses.CONFIRMED, - description: "Sabbatical", - start_date: "2021-08-01", - end_date: "2021-12-31", - created_at: "2021-08-01", - updated_at: "2021-08-01", - }, - 2: { - id: 2, - user_id: 210, - termIds: [2], - type: T.leaveTypes.SABBATICAL, - status: T.leaveStatuses.CONFIRMED, - description: "Sabbatical", - start_date: "2022-01-01", - end_date: "2022-05-31", - created_at: "2022-01-01", - updated_at: "2022-01-01", - }, -}; - -export const createMockLookups = () => - cloneDeep({ - personLookup, - termLookup, - courseLookup, - sectionLookup, - enrollmentLookup, - leaveLookup, - }); From b26f70e890a2cd07ca36add0e484db7e450f8d5f Mon Sep 17 00:00:00 2001 From: James Johnson Date: Wed, 28 Feb 2024 21:23:43 -0600 Subject: [PATCH 37/47] apply filters to course table --- .../helpers/coursePlanningFilters.ts | 73 +++++++++++++++++++ .../helpers/getCourseSpreadsheetRecords.ts | 4 +- .../helpers/getCourseTableRows.ts | 45 ++++++++++-- .../helpers/getListOfTermLeaves.ts | 22 ++++++ .../helpers/getPersonTableRows.ts | 59 ++++++--------- .../helpers/getTermsWithLeaves.ts | 15 ---- .../helpers/makeCoursePlanningFilters.ts | 30 -------- 7 files changed, 155 insertions(+), 93 deletions(-) create mode 100644 resources/js/features/course-planning/helpers/coursePlanningFilters.ts create mode 100644 resources/js/features/course-planning/helpers/getListOfTermLeaves.ts delete mode 100644 resources/js/features/course-planning/helpers/getTermsWithLeaves.ts delete mode 100644 resources/js/features/course-planning/helpers/makeCoursePlanningFilters.ts diff --git a/resources/js/features/course-planning/helpers/coursePlanningFilters.ts b/resources/js/features/course-planning/helpers/coursePlanningFilters.ts new file mode 100644 index 00000000..144cfaac --- /dev/null +++ b/resources/js/features/course-planning/helpers/coursePlanningFilters.ts @@ -0,0 +1,73 @@ +import * as T from "@/types"; + +export const filterCourseByCourseLevel = + (filters: Pick) => + (course: T.Course) => { + return !filters.excludedCourseLevels.has(course.courseLevel); + }; + +export const filterCourseByCourseType = + (filters: Pick) => + (course: T.Course) => { + return !filters.excludedCourseTypes.has(course.courseType); + }; + +export const filterSectionByPublishStateWhenNotInPlanningMode = + (filters: Pick) => + (section: T.CourseSection) => { + return filters.inPlanningMode || section.isPublished; + }; + +export const filterPersonByAcadAppt = + (filters: T.CoursePlanningFilters) => (person: T.Person) => { + return !( + filters?.excludedAcadAppts?.has(person.academicAppointment) ?? false + ); + }; + +export const filterEnrollmentByRole = + (filters: Pick) => + (enrollment: T.Enrollment) => { + return filters.includedEnrollmentRoles.has(enrollment.role); + }; + +export const allEnrollmentFiltersPass = + (filters: T.CoursePlanningFilters) => + (enrollment: T.JoinedEnrollmentRecord) => { + return ( + filterCourseByCourseLevel(filters)(enrollment.course) && + filterCourseByCourseType(filters)(enrollment.course) && + filterSectionByPublishStateWhenNotInPlanningMode(filters)( + enrollment.section, + ) && + filterEnrollmentByRole(filters)(enrollment.enrollment) + ); + }; + +export const filterPersonTableRowForAtLeastOneEnrollment = ( + row: T.PersonTableRow, +) => { + const termRecords = row.slice(1) as T.PersonTableTermRecord[]; + return termRecords.some((termRecord) => { + return termRecord.enrollments.length > 0; + }); +}; + +export const filterTermByStartAndEndTerm = + (filters: Pick) => + (term: T.Term) => { + const isBeforeStartTerm = + filters?.startTermId && term.id < filters.startTermId; + const isAfterEndTerm = filters?.endTermId && term.id > filters.endTermId; + + return !isBeforeStartTerm && !isAfterEndTerm; + }; + +export const filterCourseTableRowForAtLeastOneEnrollment = ( + row: T.CourseTableRow, +) => { + const termRecords = row.slice(1) as T.CourseTableTermRecord[]; + return termRecords.some((termRecord) => { + return termRecord.joinedEnrollments.length > 0; + }); +}; diff --git a/resources/js/features/course-planning/helpers/getCourseSpreadsheetRecords.ts b/resources/js/features/course-planning/helpers/getCourseSpreadsheetRecords.ts index a83169b5..353e2a5f 100644 --- a/resources/js/features/course-planning/helpers/getCourseSpreadsheetRecords.ts +++ b/resources/js/features/course-planning/helpers/getCourseSpreadsheetRecords.ts @@ -1,5 +1,5 @@ import * as T from "@/types"; -import { getTermsWithLeaves } from "./getTermsWithLeaves"; +import { getListOfTermLeaves } from "./getListOfTermLeaves"; import { getCourseTableRows } from "./getCourseTableRows"; import { toCourseSpreadsheetRowRecord } from "./toCourseSpreadsheetRowRecord"; import { capitalize } from "lodash"; @@ -11,7 +11,7 @@ export function getCourseSpreadsheetRecords({ lookups: T.CoursePlanningLookups; filters: T.CoursePlanningFilters; }) { - const termLeaves = getTermsWithLeaves({ + const termLeaves = getListOfTermLeaves({ lookups, filters, }); diff --git a/resources/js/features/course-planning/helpers/getCourseTableRows.ts b/resources/js/features/course-planning/helpers/getCourseTableRows.ts index f383fa48..e9e69277 100644 --- a/resources/js/features/course-planning/helpers/getCourseTableRows.ts +++ b/resources/js/features/course-planning/helpers/getCourseTableRows.ts @@ -1,26 +1,55 @@ import * as T from "@/types"; import { getEnrollmentsForCourseInTerm } from "./getEnrollmentsForCourseInTerm"; - +import { + filterCourseByCourseLevel, + filterCourseByCourseType, + filterCourseTableRowForAtLeastOneEnrollment, + filterTermByStartAndEndTerm, + allEnrollmentFiltersPass, +} from "./coursePlanningFilters"; export function getCourseTableRows({ lookups, filters, }: { lookups: T.CoursePlanningLookups; - filters?: T.CoursePlanningFilters; + filters: T.CoursePlanningFilters; }): T.CourseTableRow[] { - const courses = Object.values(lookups.courseLookup); + const sortedCourses = Object.values(lookups.courseLookup).sort((a, b) => + a.courseCode.localeCompare(b.courseCode), + ); + + const sortedTerms = Object.values(lookups.termLookup).sort( + (a, b) => a.id - b.id, + ); + + const filteredCourses = sortedCourses.filter((course) => { + return ( + filterCourseByCourseLevel(filters)(course) && + filterCourseByCourseType(filters)(course) + ); + }); - return courses.map((course) => { - const termRecords = Object.values(lookups.termLookup).map((term) => { - const joinedEnrollments = getEnrollmentsForCourseInTerm({ + const filteredTerms = sortedTerms.filter( + filterTermByStartAndEndTerm(filters), + ); + + const rows: T.CourseTableRow[] = filteredCourses.map((course) => { + const termRecords = filteredTerms.map((term) => { + const filteredEnrollments = getEnrollmentsForCourseInTerm({ course, term, lookups, - }); + }).filter(allEnrollmentFiltersPass(filters)); - return { term, joinedEnrollments }; + return { + term, + joinedEnrollments: filteredEnrollments, + }; }); return [course, ...termRecords]; }); + + // remove rows with no enrollments + return rows.filter(filterCourseTableRowForAtLeastOneEnrollment); } diff --git a/resources/js/features/course-planning/helpers/getListOfTermLeaves.ts b/resources/js/features/course-planning/helpers/getListOfTermLeaves.ts new file mode 100644 index 00000000..97750f68 --- /dev/null +++ b/resources/js/features/course-planning/helpers/getListOfTermLeaves.ts @@ -0,0 +1,22 @@ +import * as T from "@/types"; +import { getLeavesInTerm } from "./getLeavesInTerm"; +import { filterPersonByAcadAppt } from "./coursePlanningFilters"; + +export function getListOfTermLeaves({ + lookups, + filters, +}: { + lookups: T.CoursePlanningLookups; + filters: T.CoursePlanningFilters; +}): T.TermLeaves[] { + return Object.values(lookups.termLookup).map((term) => { + const leaves = getLeavesInTerm({ lookups, term }); + + // remove leaves for people with excluded academic appointments + const filteredLeaves = leaves.filter((leave) => { + return filterPersonByAcadAppt(filters)(leave.person); + }); + + return { term, leaves: filteredLeaves }; + }); +} diff --git a/resources/js/features/course-planning/helpers/getPersonTableRows.ts b/resources/js/features/course-planning/helpers/getPersonTableRows.ts index ddf0b463..24e5f488 100644 --- a/resources/js/features/course-planning/helpers/getPersonTableRows.ts +++ b/resources/js/features/course-planning/helpers/getPersonTableRows.ts @@ -4,12 +4,11 @@ import { getLeavesForPersonInTerm } from "./getLeavesForPersonInTerm"; import { getEnrollmentsForPersonInTerm } from "./getEnrollmentsForPersonInTerm"; import { getJoinedEnrollmentRecord } from "./getJoinedEnrollmentRecord"; import { - makeFilterForCourseLevel, - makeFilterForCourseType, - makeFilterForEnrollmentRole, - makeFilterForPlanningMode, - personTableRowHasAtLeaveOneEnrollment, -} from "./makeCoursePlanningFilters"; + allEnrollmentFiltersPass, + filterPersonByAcadAppt, + filterPersonTableRowForAtLeastOneEnrollment, + filterTermByStartAndEndTerm, +} from "./coursePlanningFilters"; export function getPersonTableRows({ lookups, @@ -25,44 +24,28 @@ export function getPersonTableRows({ (a, b) => a.id - b.id, ); - const filteredPeople = sortedPeople.filter((person) => { - return !( - filters?.excludedAcadAppts?.has(person.academicAppointment) ?? false - ); - }); - - const filteredTerms = sortedTerms.filter((term) => { - const isBeforeStartTerm = - filters?.startTermId && term.id < filters.startTermId; - const isAfterEndTerm = filters?.endTermId && term.id > filters.endTermId; + const filteredPeople = sortedPeople.filter(filterPersonByAcadAppt(filters)); - return !isBeforeStartTerm && !isAfterEndTerm; - }); + const filteredTerms = sortedTerms.filter( + filterTermByStartAndEndTerm(filters), + ); const rows: T.PersonTableRow[] = filteredPeople.map((person) => { - const termRecords = Object.values(filteredTerms).map((term) => { - const personEnrollmentsInTerm = getEnrollmentsForPersonInTerm({ + const termRecords = filteredTerms.map((term) => { + const filteredEnrollmentRecords = getEnrollmentsForPersonInTerm({ person, term, lookups, - }).map((enrollment) => + }) // join with other data - getJoinedEnrollmentRecord({ - enrollment, - lookups, - }), - ); - - const allFiltersPass = (record: T.JoinedEnrollmentRecord) => - [ - makeFilterForCourseLevel(filters), - makeFilterForCourseType(filters), - makeFilterForPlanningMode(filters), - makeFilterForEnrollmentRole(filters), - ].every((filter) => filter(record)); - - const filteredEnrollmentRecords = - personEnrollmentsInTerm.filter(allFiltersPass); + .map((enrollment) => + getJoinedEnrollmentRecord({ + enrollment, + lookups, + }), + ) + // exclude records that don't pass all filters + .filter(allEnrollmentFiltersPass(filters)); const personLeavesInTerm = getLeavesForPersonInTerm({ person, @@ -81,5 +64,5 @@ export function getPersonTableRows({ }); // remove rows with no enrollments or leaves - return rows.filter(personTableRowHasAtLeaveOneEnrollment); + return rows.filter(filterPersonTableRowForAtLeastOneEnrollment); } diff --git a/resources/js/features/course-planning/helpers/getTermsWithLeaves.ts b/resources/js/features/course-planning/helpers/getTermsWithLeaves.ts deleted file mode 100644 index e9b2cc12..00000000 --- a/resources/js/features/course-planning/helpers/getTermsWithLeaves.ts +++ /dev/null @@ -1,15 +0,0 @@ -import * as T from "@/types"; -import { getLeavesInTerm } from "./getLeavesInTerm"; - -export function getTermsWithLeaves({ - lookups, - filters, -}: { - lookups: T.CoursePlanningLookups; - filters?: T.CoursePlanningFilters; -}): T.TermLeaves[] { - return Object.values(lookups.termLookup).map((term) => { - const leaves = getLeavesInTerm({ lookups, term }); - return { term, leaves }; - }); -} diff --git a/resources/js/features/course-planning/helpers/makeCoursePlanningFilters.ts b/resources/js/features/course-planning/helpers/makeCoursePlanningFilters.ts deleted file mode 100644 index 3eed336b..00000000 --- a/resources/js/features/course-planning/helpers/makeCoursePlanningFilters.ts +++ /dev/null @@ -1,30 +0,0 @@ -import * as T from "@/types"; - -export const makeFilterForCourseLevel = - (filters: Pick) => - (record: T.JoinedEnrollmentRecord) => - !filters.excludedCourseLevels.has(record.course.courseLevel); - -export const makeFilterForCourseType = - (filters: Pick) => - (record: T.JoinedEnrollmentRecord) => - !filters.excludedCourseTypes.has(record.course.courseType); - -export const makeFilterForPlanningMode = - (filters: Pick) => - (record: T.JoinedEnrollmentRecord) => - filters.inPlanningMode || record.section.isPublished; - -export const makeFilterForEnrollmentRole = - (filters: Pick) => - (record: T.JoinedEnrollmentRecord) => - filters.includedEnrollmentRoles.has(record.enrollment.role); - -export const personTableRowHasAtLeaveOneEnrollment = ( - row: T.PersonTableRow, -) => { - const termRecords = row.slice(1) as T.PersonTableTermRecord[]; - return termRecords.some((termRecord) => { - return termRecord.enrollments.length > 0; - }); -}; From ef490d7485788300df69dc36c1868ea6ecaceb86 Mon Sep 17 00:00:00 2001 From: James Johnson Date: Wed, 28 Feb 2024 21:40:03 -0600 Subject: [PATCH 38/47] fix leaves not appearing in table --- .../CourseTable/CourseTableLeavesRow.vue | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/resources/js/features/course-planning/components/CourseTable/CourseTableLeavesRow.vue b/resources/js/features/course-planning/components/CourseTable/CourseTableLeavesRow.vue index 0ac6bbcd..39f9391b 100644 --- a/resources/js/features/course-planning/components/CourseTable/CourseTableLeavesRow.vue +++ b/resources/js/features/course-planning/components/CourseTable/CourseTableLeavesRow.vue @@ -1,14 +1,13 @@ diff --git a/resources/js/features/course-planning/workers/coursePlanningWorker.ts b/resources/js/features/course-planning/workers/coursePlanningWorker.ts index 3e05c993..68fa4325 100644 --- a/resources/js/features/course-planning/workers/coursePlanningWorker.ts +++ b/resources/js/features/course-planning/workers/coursePlanningWorker.ts @@ -1,24 +1,8 @@ import * as T from "@/types"; -import { getPersonTableRows } from "../helpers/getPersonTableRows"; -import { getCourseTableRows } from "../helpers/getCourseTableRows"; -import { getListOfTermLeaves } from "../helpers/getListOfTermLeaves"; +import { getPersonSpreadsheetRecords } from "../helpers/getPersonSpreadsheetRecords"; import { deserializeCoursePlanningFilters } from "../helpers/serializedCoursePlanningFilters"; - -export const MESSAGE_TYPES = { - INSTRUCTOR_TABLE_REQUEST: "INSTRUCTOR_TABLE_REQUEST", - INSTRUCTOR_TABLE_SUCCESS: "INSTRUCTOR_TABLE_SUCCESS", - INSTRUCTOR_TABLE_FAILURE: "INSTRUCTOR_TABLE_FAILURE", - TA_TABLE_REQUEST: "TA_TABLE_REQUEST", - TA_TABLE_SUCCESS: "TA_TABLE_SUCCESS", - TA_TABLE_FAILURE: "TA_TABLE_FAILURE", - COURSES_TABLE_REQUEST: "COURSES_TABLE_REQUEST", - COURSES_TABLE_SUCCESS: "COURSES_TABLE_SUCCESS", - COURSES_TABLE_FAILURE: "COURSES_TABLE_FAILURE", - LIST_OF_TERM_LEAVES_REQUEST: "LIST_OF_TERM_LEAVES_REQUEST", - LIST_OF_TERM_LEAVES_SUCCESS: "LIST_OF_TERM_LEAVES_SUCCESS", - LIST_OF_TERM_LEAVES_FAILURE: "LIST_OF_TERM_LEAVES_FAILURE", - INVALID_MESSAGE_TYPE: "INVALID_MESSAGE_TYPE", -} as const; +import * as MESSAGE_TYPES from "./messageTypes"; +import { getCourseSpreadsheetRecords } from "../helpers/getCourseSpreadsheetRecords"; export interface CoursePlanningData { lookups: T.CoursePlanningLookups; @@ -31,13 +15,13 @@ export interface WorkerMessage { error?: TError; } -const messageHandlers = { - [MESSAGE_TYPES.INSTRUCTOR_TABLE_REQUEST]: ({ +const requestHandlers = { + [MESSAGE_TYPES.INSTRUCTOR_SPREADSHEET_REQUEST]: ({ lookups, serializedFilters, }: CoursePlanningData) => { const filters = deserializeCoursePlanningFilters(serializedFilters); - return getPersonTableRows({ + return getPersonSpreadsheetRecords({ lookups, filters: { ...filters, @@ -46,30 +30,26 @@ const messageHandlers = { }); }, - // [MESSAGE_TYPES.TA_TABLE_REQUEST]: ({ - // lookups, - // filters, - // }: CoursePlanningData) => { - // return getPersonTableRows({ - // lookups, - // filters: { - // ...filters, - // includedEnrollmentRoles: new Set(["TA"]), - // }, - // }); - // }, - // [MESSAGE_TYPES.COURSES_TABLE_REQUEST]: ({ - // lookups, - // filters, - // }: CoursePlanningData) => { - // return getCourseTableRows({ lookups, filters }); - // }, - // [MESSAGE_TYPES.LIST_OF_TERM_LEAVES_REQUEST]: ({ - // lookups, - // filters, - // }: CoursePlanningData) => { - // return getListOfTermLeaves({ lookups, filters }); - // }, + [MESSAGE_TYPES.TA_SPREADSHEET_REQUEST]: ({ + lookups, + serializedFilters, + }: CoursePlanningData) => { + const filters = deserializeCoursePlanningFilters(serializedFilters); + return getPersonSpreadsheetRecords({ + lookups, + filters: { + ...filters, + includedEnrollmentRoles: new Set(["TA"]), + }, + }); + }, + [MESSAGE_TYPES.COURSES_SPREADSHEET_REQUEST]: ({ + lookups, + serializedFilters, + }: CoursePlanningData) => { + const filters = deserializeCoursePlanningFilters(serializedFilters); + return getCourseSpreadsheetRecords({ lookups, filters }); + }, }; function getFailureMessageType( @@ -93,9 +73,9 @@ function getSuccessMessageType( self.addEventListener("message", async (event: MessageEvent) => { const { type, payload } = event.data; - const messageHandler = messageHandlers[type]; + const requestHandler = requestHandlers[type]; - if (!messageHandler) { + if (!requestHandler) { self.postMessage({ type: MESSAGE_TYPES.INVALID_MESSAGE_TYPE, error: `Unknown message type: ${type}`, @@ -104,7 +84,7 @@ self.addEventListener("message", async (event: MessageEvent) => { } try { - const response = await messageHandler(payload); + const response = await requestHandler(payload); self.postMessage({ type: getSuccessMessageType(type), payload: response, diff --git a/resources/js/features/course-planning/workers/getSpreadsheetFromWorker.ts b/resources/js/features/course-planning/workers/getSpreadsheetFromWorker.ts new file mode 100644 index 00000000..02dc3af4 --- /dev/null +++ b/resources/js/features/course-planning/workers/getSpreadsheetFromWorker.ts @@ -0,0 +1,54 @@ +import * as T from "@/types"; +import { cloneDeep } from "lodash"; +import ViteWorker from "@/utils/ViteWorker"; +import coursePlanningWorkerURL from "./coursePlanningWorker?worker&url"; +import { useCoursePlanningStore } from "../stores"; +import { serializedCoursePlanningFilters } from "../helpers/serializedCoursePlanningFilters"; +import * as MESSAGE_TYPES from "./messageTypes"; + +type SpreadsheetRequestType = + | typeof MESSAGE_TYPES.INSTRUCTOR_SPREADSHEET_REQUEST + | typeof MESSAGE_TYPES.COURSES_SPREADSHEET_REQUEST + | typeof MESSAGE_TYPES.TA_SPREADSHEET_REQUEST; + +export function getSpreadsheetFromWorker( + requestType: SpreadsheetRequestType, +): Promise { + const coursePlanningStore = useCoursePlanningStore(); + + return new Promise((resolve, reject) => { + const worker = new ViteWorker(coursePlanningWorkerURL); + const lookups = cloneDeep(coursePlanningStore.getCoursePlanningLookups()); + const filters = coursePlanningStore.getCoursePlanningFilters(); + const serializedFilters = serializedCoursePlanningFilters(filters); + + worker.addEventListener("message", (event) => { + const { payload, error, type } = (event as MessageEvent).data; + + if (type.includes("SUCCESS")) { + resolve(payload ?? []); + } + + if (type.includes("FAILURE")) { + console.error(error); + reject(error); + } + + if (type === MESSAGE_TYPES.INVALID_MESSAGE_TYPE) { + console.error(error); + reject(error); + } + + worker.terminate(); + worker.removeAllEventListeners(); + }); + + worker.postMessage({ + type: requestType, + payload: { + lookups, + serializedFilters, + }, + }); + }); +} diff --git a/resources/js/features/course-planning/workers/messageTypes.ts b/resources/js/features/course-planning/workers/messageTypes.ts new file mode 100644 index 00000000..81ac0f7d --- /dev/null +++ b/resources/js/features/course-planning/workers/messageTypes.ts @@ -0,0 +1,10 @@ +export const INSTRUCTOR_SPREADSHEET_REQUEST = "INSTRUCTOR_SPREADSHEET_REQUEST"; +export const INSTRUCTOR_SPREADSHEET_SUCCESS = "INSTRUCTOR_SPREADSHEET_SUCCESS"; +export const INSTRUCTOR_SPREADSHEET_FAILURE = "INSTRUCTOR_SPREADSHEET_FAILURE"; +export const TA_SPREADSHEET_REQUEST = "TA_SPREADSHEET_REQUEST"; +export const TA_SPREADSHEET_SUCCESS = "TA_SPREADSHEET_SUCCESS"; +export const TA_SPREADSHEET_FAILURE = "TA_SPREADSHEET_FAILURE"; +export const COURSES_SPREADSHEET_REQUEST = "COURSES_SPREADSHEET_REQUEST"; +export const COURSES_SPREADSHEET_SUCCESS = "COURSES_SPREADSHEET_SUCCESS"; +export const COURSES_SPREADSHEET_FAILURE = "COURSES_SPREADSHEET_FAILURE"; +export const INVALID_MESSAGE_TYPE = "INVALID_MESSAGE_TYPE"; diff --git a/resources/js/utils/ViteWorker.ts b/resources/js/utils/ViteWorker.ts new file mode 100644 index 00000000..58dc9358 --- /dev/null +++ b/resources/js/utils/ViteWorker.ts @@ -0,0 +1,59 @@ +// a workaround for creating web workers in vite with typescript +// see: https://github.com/vitejs/vite/issues/13680#issuecomment-1819274694 + +export default class ViteWorker extends Worker { + objectUrl: string | null = null; + eventListeners = new Map(); + + constructor(workerURL: string, workerOptions: WorkerOptions = {}) { + const js = `import ${JSON.stringify(new URL(workerURL, import.meta.url))}`; + const blob = new Blob([js], { type: "application/javascript" }); + const objectURL = URL.createObjectURL(blob); + + super(objectURL, { type: "module", ...workerOptions }); + + this.objectUrl = objectURL; + } + + terminate() { + super.terminate(); + if (this.objectUrl) { + URL.revokeObjectURL(this.objectUrl); + } + } + + // track event listeners for cleanup + addEventListener( + type: string, + listener: EventListener, + options?: boolean | AddEventListenerOptions, + ): void { + const currentListeners = this.eventListeners.get(type) || []; + + this.eventListeners.set(type, [...currentListeners, listener]); + return super.addEventListener(type, listener, options); + } + + removeEventListener(type: string, listener: EventListener) { + const currentListeners = this.eventListeners.get(type); + + if (!currentListeners) { + throw new Error(`No listeners found for event type: ${type}`); + } + + this.eventListeners.set( + type, + currentListeners.filter((l) => l !== listener), + ); + + super.removeEventListener(type, listener); + } + + removeAllEventListeners() { + for (const [type, listeners] of this.eventListeners.entries()) { + for (const listener of listeners) { + this.removeEventListener(type, listener); + } + } + } +} From eadb681df8d049d85aa7ad517601b20a5f20150e Mon Sep 17 00:00:00 2001 From: James Johnson Date: Thu, 29 Feb 2024 16:25:55 -0600 Subject: [PATCH 45/47] fix: reset filters when group changes --- .../course-planning/stores/useCoursePlanningStore.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/resources/js/features/course-planning/stores/useCoursePlanningStore.ts b/resources/js/features/course-planning/stores/useCoursePlanningStore.ts index b6442f2c..2976e6b7 100644 --- a/resources/js/features/course-planning/stores/useCoursePlanningStore.ts +++ b/resources/js/features/course-planning/stores/useCoursePlanningStore.ts @@ -239,11 +239,7 @@ export const useCoursePlanningStore = defineStore("coursePlanning", () => { stores.leaveStore.init(groupId), ]); - const earliestTerm = stores.termsStore.earliestTerm; - const latestTerm = stores.termsStore.latestTerm; - - this.setStartTermId(earliestTerm?.id ?? null); - this.setEndTermId(latestTerm?.id ?? null); + this.resetFilters(); }, setStartTermId(termId: T.Term["id"] | null) { From a971b2f1fe5fa12cc823d3141b36da935c7b7079 Mon Sep 17 00:00:00 2001 From: James Johnson Date: Thu, 29 Feb 2024 17:14:08 -0600 Subject: [PATCH 46/47] Add simulated progress and logging for sheet completion --- .../components/DownloadSpreadsheetButton.vue | 7 ++++- .../helpers/useSimulatedProgress.ts | 27 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 resources/js/features/course-planning/helpers/useSimulatedProgress.ts diff --git a/resources/js/components/DownloadSpreadsheetButton.vue b/resources/js/components/DownloadSpreadsheetButton.vue index 2d8cfff7..b5ab5b38 100644 --- a/resources/js/components/DownloadSpreadsheetButton.vue +++ b/resources/js/components/DownloadSpreadsheetButton.vue @@ -10,6 +10,7 @@