diff --git a/src/client/pages/CourseSummary/SummaryV2/MyOrganisations.js b/src/client/pages/CourseSummary/SummaryV2/MyOrganisations.js index 285d09764..4d5d4fa3d 100644 --- a/src/client/pages/CourseSummary/SummaryV2/MyOrganisations.js +++ b/src/client/pages/CourseSummary/SummaryV2/MyOrganisations.js @@ -1,38 +1,41 @@ import React from 'react' -import { Box } from '@mui/material' +import { Box, LinearProgress } from '@mui/material' import { OrganisationSummaryRow, SorterRow } from './SummaryRow' -import useAuthorizedUser from '../../../hooks/useAuthorizedUser' import { useSummaryContext } from './context' - -const useRootOrganisations = organisations => { - const rootOrganisations = React.useMemo(() => organisations.map(o => o.organisation), [organisations]) - - return rootOrganisations -} +import { useOrganisationSummaries } from './api' +import { useOrderedAndFilteredOrganisations } from './utils' /** * */ const MyOrganisations = () => { - const { authorizedUser: user } = useAuthorizedUser() - const rootOrganisations = useRootOrganisations(user.organisations) const { dateRange } = useSummaryContext() + const { organisations, isLoading } = useOrganisationSummaries({ + startDate: dateRange.start, + endDate: dateRange.end, + enabled: true, + }) + + const orderedAndFilteredOrganisations = useOrderedAndFilteredOrganisations(organisations) return ( - {rootOrganisations.map((organisation, i) => ( - - ))} + {isLoading ? ( + + ) : ( + orderedAndFilteredOrganisations.map(organisation => ( + + )) + )} ) } diff --git a/src/client/pages/CourseSummary/SummaryV2/SummaryRow.js b/src/client/pages/CourseSummary/SummaryV2/SummaryRow.js index 641304dab..c9cd15e29 100644 --- a/src/client/pages/CourseSummary/SummaryV2/SummaryRow.js +++ b/src/client/pages/CourseSummary/SummaryV2/SummaryRow.js @@ -11,7 +11,7 @@ import SummaryResultItem from '../../../components/SummaryResultItem/SummaryResu import { CourseUnitLabel, OrganisationLabel } from '../Labels' import PercentageCell from '../PercentageCell' import useRandomColor from '../../../hooks/useRandomColor' -import { useSummaryQuestions } from './utils' +import { useOrderedAndFilteredOrganisations, useSummaryQuestions } from './utils' import { useSummaryContext } from './context' import Sort from './Sort' @@ -257,7 +257,6 @@ const CourseUnitSummaryRow = ({ courseUnit, questions }) => { } const ChildOrganisationsList = ({ organisationId, startDate, endDate, questions }) => { - const { showSummariesWithNoFeedback, sortBy, sortFunction } = useSummaryContext() const { organisation, isLoading } = useSummaries({ entityId: organisationId, startDate, @@ -265,23 +264,13 @@ const ChildOrganisationsList = ({ organisationId, startDate, endDate, questions include: 'childOrganisations', }) - const filteredAndOrderedOrganisations = React.useMemo( - () => - _.orderBy( - showSummariesWithNoFeedback - ? organisation?.childOrganisations - : organisation?.childOrganisations?.filter(org => !!org.summary), - org => sortFunction(org.summary), - sortBy[1] - ), - [showSummariesWithNoFeedback, organisation, sortBy[0], sortBy[1]] - ) + const orderedAndFilteredOrganisations = useOrderedAndFilteredOrganisations(organisation?.childOrganisations) if (isLoading) { return } - return filteredAndOrderedOrganisations?.map(org => ( + return orderedAndFilteredOrganisations?.map(org => ( { alwaysOpen startDate={dateRange.start} endDate={dateRange.end} - hasSorter /> ) diff --git a/src/client/pages/CourseSummary/SummaryV2/api.js b/src/client/pages/CourseSummary/SummaryV2/api.js index 62a5b72ee..b807a85a1 100644 --- a/src/client/pages/CourseSummary/SummaryV2/api.js +++ b/src/client/pages/CourseSummary/SummaryV2/api.js @@ -3,6 +3,11 @@ import apiClient from '../../../util/apiClient' const TWELVE_HOURS = 1000 * 60 * 60 * 12 +/** + * Fetches a summary row for an organisation. + * include can be 'childOrganisations' or 'courseUnits', in which case the organisation's + * corresponding children are included in the response, allowing the row to be expanded. + */ export const useSummaries = ({ startDate, endDate, entityId, enabled, include }) => { const queryKey = ['summaries-v2', entityId, startDate, endDate, include] @@ -32,11 +37,45 @@ export const useSummaries = ({ startDate, endDate, entityId, enabled, include }) return { organisation, ...rest } } +/** + * Fetches all summaries for a teacher based on their courses. + * Returns a list of organisations with course units as children. + */ export const useTeacherSummaries = ({ startDate, endDate, enabled }) => { const queryKey = ['summaries-v2-teacher', startDate, endDate] const queryFn = async () => { - const { data } = await apiClient.get(`course-summaries/courses-v2`, { + const { data } = await apiClient.get(`course-summaries/user-courses-v2`, { + params: { + startDate, + endDate, + }, + }) + + return data + } + + const { data, ...rest } = useQuery(queryKey, queryFn, { + enabled, + retry: false, + refetchOnWindowFocus: false, + keepPreviousData: true, + staleTime: TWELVE_HOURS, + }) + + const organisations = data || [] + + return { organisations, ...rest } +} + +/** + * Fetches all organisation rows for user based on their org access + */ +export const useOrganisationSummaries = ({ startDate, endDate, enabled }) => { + const queryKey = ['summaries-v2-organisations', startDate, endDate] + + const queryFn = async () => { + const { data } = await apiClient.get(`course-summaries/user-organisations-v2`, { params: { startDate, endDate, diff --git a/src/client/pages/CourseSummary/SummaryV2/context.js b/src/client/pages/CourseSummary/SummaryV2/context.js index fe1432d0a..d2e97a08f 100644 --- a/src/client/pages/CourseSummary/SummaryV2/context.js +++ b/src/client/pages/CourseSummary/SummaryV2/context.js @@ -92,10 +92,12 @@ export const SummaryContextProvider = ({ children }) => { }) const updateSortByQS = React.useCallback(sortBy => { - setSortBy(sortBy) - params.set('sortBy', sortBy[0]) - params.set('order', sortBy[1]) - setParams(params) + React.startTransition(() => { + setSortBy(sortBy) + params.set('sortBy', sortBy[0]) + params.set('order', sortBy[1]) + setParams(params) + }) }) const sortFunction = React.useMemo(() => getSummarySortFunction(sortBy[0]), [sortBy[0]]) diff --git a/src/client/pages/CourseSummary/SummaryV2/utils.js b/src/client/pages/CourseSummary/SummaryV2/utils.js index cb3695c66..963a291a0 100644 --- a/src/client/pages/CourseSummary/SummaryV2/utils.js +++ b/src/client/pages/CourseSummary/SummaryV2/utils.js @@ -1,4 +1,7 @@ +import React from 'react' +import _ from 'lodash' import useUniversitySurvey from '../../../hooks/useUniversitySurvey' +import { useSummaryContext } from './context' export const useSummaryQuestions = () => { const { survey: universitySurvey, isLoading: isUniversitySurveyLoading } = useUniversitySurvey() @@ -12,3 +15,18 @@ export const useSummaryQuestions = () => { isLoading: isUniversitySurveyLoading, } } + +export const useOrderedAndFilteredOrganisations = organisations => { + const { showSummariesWithNoFeedback, sortBy, sortFunction } = useSummaryContext() + const filteredAndOrderedOrganisations = React.useMemo( + () => + _.orderBy( + showSummariesWithNoFeedback ? organisations : organisations.filter(org => !!org.summary), + org => sortFunction(org.summary), + sortBy[1] + ), + [showSummariesWithNoFeedback, organisations, sortBy[0], sortBy[1]] + ) + + return filteredAndOrderedOrganisations +} diff --git a/src/server/routes/courseSummary/courseSummaryController.js b/src/server/routes/courseSummary/courseSummaryController.js index d9c77e5c4..bc2a23276 100644 --- a/src/server/routes/courseSummary/courseSummaryController.js +++ b/src/server/routes/courseSummary/courseSummaryController.js @@ -1,6 +1,5 @@ const { Router } = require('express') const { addYears, format } = require('date-fns') -const { Op } = require('sequelize') const { CourseUnit, Organisation } = require('../../models') @@ -17,6 +16,7 @@ const { getOrganisationSummaryWithCourseUnits, getOrganisationSummary, getTeacherSummary, + getUserOrganisationSummaries, } = require('../../services/summary/summaryV2') const { startOfStudyYear, endOfStudyYear } = require('../../util/common') const { inProduction } = require('../../util/config') @@ -250,11 +250,27 @@ const getCoursesV2 = async (req, res) => { return res.send(organisations) } +const getUserOrganisationsV2 = async (req, res) => { + const { startDate: startDateString, endDate: endDateString } = req.query + const { user } = req + + const { startDate, endDate } = parseDates(startDateString, endDateString) + + const organisations = await getUserOrganisationSummaries({ + user, + startDate, + endDate, + }) + + res.send(organisations) +} + const router = Router() router.get('/organisations', getOrganisations) router.get('/organisations-v2', getOrganisationsV2) -router.get('/courses-v2', getCoursesV2) +router.get('/user-courses-v2', getCoursesV2) +router.get('/user-organisations-v2', getUserOrganisationsV2) router.get('/organisations/:code', getOrganisations) router.get('/course-units/:code', getByCourseUnit) router.get('/access', getAccessInfo) diff --git a/src/server/services/summary/summaryV2.js b/src/server/services/summary/summaryV2.js index ea20cbd6a..d0cea7244 100644 --- a/src/server/services/summary/summaryV2.js +++ b/src/server/services/summary/summaryV2.js @@ -325,9 +325,37 @@ const getTeacherSummary = async ({ startDate, endDate, user }) => { return organisationsJson } +const getUserOrganisationSummaries = async ({ startDate, endDate, user }) => { + const organisationIds = await getSummaryAccessibleOrganisationIds(user) + + const organisations = await Organisation.findAll({ + attributes: ['id', 'name', 'code'], + where: { + id: { + [Op.in]: organisationIds, + [Op.notIn]: SUMMARY_EXCLUDED_ORG_IDS, + }, + }, + include: { + model: Summary.scope({ method: ['at', startDate, endDate] }), + as: 'summaries', + required: false, + }, + }) + + const organisationsJson = organisations.map(org => { + org.summary = sumSummaries(org.summaries) + delete org.dataValues.summaries + return org.toJSON() + }) + + return organisationsJson +} + module.exports = { getOrganisationSummary, getOrganisationSummaryWithChildOrganisations, getOrganisationSummaryWithCourseUnits, getTeacherSummary, + getUserOrganisationSummaries, }